Rvalue references and constructor arguments

Consider a simple class

template< typename T > class Wrapper { public: // Constructors? private: T wrapped; }; 

What designers should use to be effective?


Prior to C ++ 0x, there would be a constructor that accepts either:

  • const reference ( T const& ) - if type T is heavy,
  • or value ( T ) - if type T is light.

Determining whether type T “heavy” or “light” is not easy.

It can be assumed that only built-in types (ints / floats / ...) are "light". But this is not entirely correct, since our own Wrapper<int> should most likely also be considered "light."

Libraries, such as boost::call_traits , provide some means to overcome this difficulty by allowing the creator type to mark the type as "light" (by providing proper call_traits specialization). Otherwise, it will be considered as "heavy." Seems acceptable.


But C ++ 0x makes it worse. Because now you have an rvalue ( T&& ) link that allows you to efficiently accept (some) "heavy" objects.

And because of this, now you have to choose among:

  • just const reference ( T const& ) - if the type T is "heavy" and does not support the semantics of movement (because either not - like with large PODs), or none of them were written, and you have no influence on this) ,
  • both const reference ( T const& ) and rvalue reference ( T&& ) - if the type T "heavy" and supports the semantics of movement,
  • just value ( T ) - if the type T "light" or "heavy", but supports moving semantics (even if the copy is made, it does not require use, because otherwise we would have to copy from T const& anyway ...) .

However, it is not easy to say which types are "heavy" and which are "light" (as before). But now you also cannot determine whether type T supports transfer semantics or not (or you?).


This becomes even more annoying if you wrap more than one value, as the number of possible constructor overloads grows exponentially.

Is there any solution to this problem?

I’m at least talking about some template designers for sending arguments (flawlessly redirecting) the arguments, but I was not sure if this would work as desired. And also it would allow us to provide values ​​of various types that would simply be sent to the T constructor. This can be considered a sign, but not required.

+4
source share
2 answers

In contrast, C ++ 11 simplifies the use of 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 second default argument, which only exists when T is constructive from U , so as not to make your class itself constructive from unmatching types. And make it also variation:

 template <typename ...Args> Wrapper(Args &&... args, typename std::enable_if<std::is_constructible<T, Args...>::value, int>::type = 0) : value(std::forward<Args>(args)...) { } 

Mandatory #include <utility> for forward and #include <type_traits> for these signs.

+7
source

If you are going to copy your T in any case, it is better to pass the parameter by value and let the compiler understand what copying is. Whatever you do, in any case there will be at least one copy.

 template< typename T > class Wrapper { public: Wrapper(T value) : wrapped(std::move(value)) { } private: T wrapped; }; 

See Want speed? Pass by value by Dave Abraham.

+3
source

All Articles