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:
- const reference (
T const&) – if typeTis “heavy”, - or value (
T) – if typeTis “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:
- just const reference (
T const&) – if typeTis “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), - both const reference (
T const&) and rvalue reference (T&&) – if typeTis “heavy” and does support move semantics, - just value (
T) – if typeTis “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 fromT 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:
As an extra nice touch, you should add a defaulted second argument that only exists when
Tis constructible fromU, so as to not make your class itself appear constructible from unmatching types. And make it variadic, too:Make sure to
#include <utility>forforwardand#include <type_traits>for the traits.