In the interest of clarifying: The pass-by-value answers are not wrong. But neither is your first guess of adding a string&&
overload, except for one detail:
Add:
X (std::string&& s) : S(std::move(s)) { }
I.e. you still need the move
because although s
has a declared type of rvalue reference to string
, the expression s
used to initialize S
is an lvalue expression of type string
.
Indeed, the solution you first proposed (with the move added) is slightly faster than the pass-by-value solution. But both are correct. The pass-by-value solution calls string's move constructor an extra time when passing lvalue and xvalue arguments to X's constructor.
In the case of lvalue arguments, a copy is made anyway, and string's copy constructor is likely to be much more expensive than string's move constructor (except for strings that fit within the short string buffer, in which case move and copy are approximately the same speed).
In the case of xvalue arguments (an xvalue is an lvalue that has been passed to std::move
), the pass-by-value solution requires two move constructions instead of one. So it is twice as expensive as the pass by rvalue reference solution. But still very fast.
The point of this post is to make clear: pass-by-value is an acceptable solution. But it is not the only acceptable solution. Overloading with pass-by-rvalue-ref is faster but has the disadvantage that the number of overloads required scales as 2^N as the number of arguments N grows.