Adam Badura
Adam Badura

Reputation: 5339

rvalue references and constructor arguments

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:

  1. const reference (T const&) - if type T is "heavy",
  2. or value (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:

  1. just const reference (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),
  2. both const reference (T const&) and rvalue reference (T&&) - if type T is "heavy" and does support move semantics,
  3. just value (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.

Upvotes: 4

Views: 1123

Answers (2)

Bo Persson
Bo Persson

Reputation: 92271

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.

Upvotes: 3

Kerrek SB
Kerrek SB

Reputation: 477060

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.

Upvotes: 7

Related Questions