Is the pass-by-value-and-then-move construct a bad idiom?

Since we have move semantics in C++, nowadays it is usual to do

void set_a(A a) { _a = std::move(a); }

The reasoning is that if a is an rvalue, the copy will be elided and there will be just one move.

But what happens if a is an lvalue? It seems there will be a copy construction and then a move assignment (assuming A has a proper move assignment operator). Move assignments can be costly if the object has too many member variables.

On the other hand, if we do

void set_a(const A& a) { _a = a; }

There will be just one copy assignment. Can we say this way is preferred over the pass-by-value idiom if we will pass lvalues?


Expensive-to-move types are rare in modern C++ usage. If you are concerned about the cost of the move, write both overloads:

void set_a(const A& a) { _a = a; }
void set_a(A&& a) { _a = std::move(a); }

or a perfect-forwarding setter:

template <typename T>
void set_a(T&& a) { _a = std::forward<T>(a); }

that will accept lvalues, rvalues, and anything else implicitly convertible to decltype(_a) without requiring extra copies or moves.

Despite requiring an extra move when setting from an lvalue, the idiom is not bad since (a) the vast majority of types provide constant-time moves and (b) copy-and-swap provides exception safety and near-optimal performance in a single line of code.


But what happens if a is an lvalue? It seems there will be a copy construction and then a move assignment (assuming A has a proper move assignment operator). Move assignments can be costly if the object has too many member variables.

Problem well spotted. I wouldn't go as far as to say that the pass-by-value-and-then-move construct is a bad idiom but it definitely has its potential pitfalls.

If your type is expensive to move and / or moving it is essentially just a copy, then the pass-by-value approach is suboptimal. Examples of such types would include types with a fixed size array as a member: It may be relatively expensive to move and a move is just a copy. See also

  • Small String Optimization and Move Operations and
  • "Want speed? Measure." (by Howard Hinnant)

in this context.

The pass-by-value approach has the advantage that you only need to maintain one function but you pay for this with performance. It depends on your application whether this maintenance advantage outweighs the loss in performance.

The pass by lvalue and rvalue reference approach can lead to maintenance headaches quickly if you have multiple arguments. Consider this:

#include <vector>
using namespace std;

struct A { vector<int> v; };
struct B { vector<int> v; };

struct C {
  A a;
  B b;
  C(const A&  a, const B&  b) : a(a), b(b) { }
  C(const A&  a,       B&& b) : a(a), b(move(b)) { }
  C(      A&& a, const B&  b) : a(move(a)), b(b) { }
  C(      A&& a,       B&& b) : a(move(a)), b(move(b)) { }  
};

If you have multiple arguments, you will have a permutation problem. In this very simple example, it is probably still not that bad to maintain these 4 constructors. However, already in this simple case, I would seriously consider using the pass-by-value approach with a single function

C(A a, B b) : a(move(a)), b(move(b)) { }

instead of the above 4 constructors.

So long story short, neither approach is without drawbacks. Make your decisions based on actual profiling information, instead of optimizing prematurely.