C++: std::move with rvalue reference is not moving contents

explicit Trade(vecstr_t&& vec) : _vec(vec)
{}

In the constructor above, even though vec is of type rvalue reference to vecstr_t, it is itself an lvalue. The basic rule to remember is – if it has a name, it’s an lvalue.

There are very few contexts where an lvalue may automatically be moved from (such as the return statement of a function that returns an object by value), but a constructor’s mem-initializer list is not one of them.

In your example, _vec is copy constructed from vec. If you want it to be move constructed instead, use std::move.

explicit Trade(vecstr_t&& vec) : _vec(std::move(vec))
{}

Now the second call to print will not print anything. Note that technically the second call could print a non-zero size because the contents of a moved from vector are unspecified. But on most (probably all) implementations, you’ll see an empty vector.

Live demo


Your comment below says your intent is to accept both rvalues and lvalues, move only in the case of the former, and copy the argument otherwise. As currently written, your constructor will only accept rvalues, and not lvalues. There are a few different options to achieve what you want.

The easiest probably is to change the parameter so that it’s taking the argument by value, and then unconditionally move.

explicit Trade(vecstr_t vec) : _vec(std::move(vec))
{}

The drawback with this approach is that you may incur an additional move construction of the vector, but move constructing a vector is cheap, and you should go with this option in most cases.

The second option is to create two overloads of the constructor

explicit Trade(vecstr_t&&      vec) : _vec(std::move(vec)) {}
explicit Trade(vecstr_t const& vec) : _vec(vec)            {}

The drawback with this one is that the number of overloads will increase exponentially as the number of constructor arguments increases.

The third option is to use perfect forwarding.

template<typename V>
explicit Trade(V&& vec) : _vec(std::forward<V>(vec)) {}

The code above will preserve the value category of the argument passed to the constructor when it forwards it to construct _vec. This means that if vec is an rvalue, the vecstr_t move constructor will be called. And if it is an lvalue, it will be copied from.

The drawback with this solution is that your constructor will accept any type of argument, not just a vecstr_t, and then the move/copy construction in the mem-initializer list will fail if the argument is not convertible to vecstr_t. This may result in error messages that are confusing to the user.

Leave a Comment