std::string get(){ // this is similar to return std::string("..."), which is // copied/moved into the return value object. return "..."; }
RVO allows it to build a temporary string object directly into the get() return value object.
foo( get() );
RVO allows you to directly build a timeline object (return value object) directly into the parameter object foo .
These are permitted RVO scripts. If your compiler cannot apply them, it should use move constructors (if any) to move the return value to the return value object and parameter object, respectively. In this case, this is not surprising, because both temporary objects are still considered or treated as rvalues. (For the first scenario, no expression matches the created temporary one, therefore processing is performed only with the aim of choosing which constructor is used to copy / move the temporary object of the return object).
In other cases, the compiler must treat things as r values, even if they are otherwise lvalues
std::string get(){ std::string s = "...";
The spectrum says that when he can potentially apply RVO (or NRVO) to an lvalue according to the rules he sets out, an implementation should treat expressions as rvalues ββand use move constructors if they are available, and only if they cannot find a suitable constructor, it should use the expression as an lvalue. It would be a pity that the programmer could write explicit moves in these cases, when he cleared the programmer, he would always want to move instead of a copy.
Example:
struct A { A(); A(A&); }; struct B { B(); B(B&&); }; A f() { A a; return a; } B f() { B b; return b; }
The former requires a as an rvalue, but cannot find constructors that accept this rvalue value ( A& cannot communicate with rvalues). Therefore, he again considers a as being (lvalue). For the second, it takes b as an rvalue and has B(B&&) takes that r value and moves it. If he took b as the value of lvalue (what it is), then the initialization of the copy would fail, because b does not have a constructor implicitly declared.
Note that returning and passing parameters use copy initialization rules, which means
u -> T (where u type is different from T) => T rvalue_tmp = u; T target(rvalue_tmp); t -> T (where t type is T) => T target = t;
Therefore, in the example where we return "..." , first create a temporary rvalue and then transfer it to the target. In the case when we return an expression of the type of the return value / parameter, we will directly move / copy the expression to the target.