Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 291
Adam Badura Avatar asked Dec 27 '22 21:12

Adam Badura


2 Answers

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.

like image 108
Kerrek SB Avatar answered Jan 07 '23 11:01

Kerrek SB


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.

like image 37
Bo Persson Avatar answered Jan 07 '23 12:01

Bo Persson