Lets consider a simple class
template< typename T >
class Wrapper {
public:
// Constructors?
private:
T wrapped;
};
What constructors should it use to be effective?
Before C++0x there would be a constructor that takes either:
T const&
) - if type T
is "heavy",T
) - if type T
is "light".Determining whether type T
is "heavy" or "light" is not easy.
One could assume only build-in types (ints/floats/...) are "light". But that is not fully correct since our own Wrapper<int>
most likely should be considered a "light" type as well.
Libraries like boost::call_traits
provide some means to overcome this difficulty by allowing type creator to mark the type as "light" (by providing proper call_traits
specialization). Otherwise it will be treated as "heavy". Seems acceptable.
But C++0x makes it worse. Because now you have also rvalue reference (T&&
) which allows to efficiently take (some) "heavy" objects.
And because of this now you have to chose among:
T const&
) - if type T
is "heavy" and does not support move semantics (because either there is none - like with large PODs - or none was written and you have no influence on that),T const&
) and rvalue reference (T&&
) - if type T
is "heavy" and does support move semantics,T
) - if type T
is "light" or if it is "heavy" but supports move semantics (even if copy is made it doesn't bother use since otherwise we would have to copy from T const&
ourselves anyway...).Still its not easy to tell which types are "heavy" and which are "light" (as previously). But now you are also unable to tell whether type T
supports move semantics or not (or are you?).
This becomes even more annoying once you wrap more than one value since number of possible constructor overloads grows exponentially.
Is there any solution to that problem?
I though about some template constructors for forwarding (perfect forwarding) arguments but I wasn't sure whether that would work as desired. And also it would allow to provide values of different type that would be just forwarded to T
constructor. This might be considered a feature but does not have to.
On the contrary, C++11 makes it easier thanks to universal references:
template <typename T> struct Wrapper
{
T value;
template <typename U> Wrapper(U && u)
: value(std::forward<U>(u))
{ }
};
As an extra nice touch, you should add a defaulted second argument that only exists when T
is constructible from U
, so as to not make your class itself appear constructible from unmatching types. And make it variadic, too:
template <typename ...Args>
Wrapper(Args &&... args,
typename std::enable_if<std::is_constructible<T, Args...>::value, int>::type = 0)
: value(std::forward<Args>(args)...)
{ }
Make sure to #include <utility>
for forward
and #include <type_traits>
for the traits.
If you are going to copy your T
anyway, it might be better to pass the parameter by value and let the compiler figure out the copying. Whatever you do, there is going to be at least one copy anyway.
template< typename T >
class Wrapper {
public:
Wrapper(T value) : wrapped(std::move(value))
{ }
private:
T wrapped;
};
See Want speed? Pass by value by Dave Abrahams.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With