I am quite puzzled by the std::move stuff. Assume I have this
piece of code:
string foo() {
string t = "xxxx";
return t;
}
string s = foo();
How many times the string constructor is called? Is it 2 or 3?
Is the compiler going to use move for this line?
string s = foo();
If so, in the function I am not even returning rvalue reference, so how could the
compiler invoke the move constructor?
It depends on the compiler. In this case, the standard requires that there will be at least one constructor call. Namely, the construction of t.
But the standard allows the possibility of two others: the move-construction of the value output of foo from t, and the move-construction of s from the value output of foo. Most decent compilers will forgo these constructors by constructing t directly in the memory for s. This optimization is made possible because the standard allows these constructors to not be called if the compiler chooses not to.
This is called copy/move "elision".
If so, in the function I am not even returning rvalue reference, so how could the compiler invoke the move constructor?
You seem to be laboring under the misconception that && means "move", and that if there's no && somewhere, then movement can't happen. Or that move construction requires move, which also is not true.
C++ is specified in such a way that certain kinds of expressions in certain places are considered valid to move from. This means that the value or reference will attempt to bind to a && parameter before binding to a & parameter. Temporaries, for example, will preferentially bind to a && parameter before a const& one. That's why temporaries used to construct values of that type will be moved from.
If you have a function which returns a value of some type T, and a return expression is of the form return x, where x is a named variable of type T of automatic storage duration (ie: a function parameter or stack variable), then the standard requires that this return expression move construct the returned value from x.
The return value of foo is a temporary. The rules of C++ require that temporaries bind to && parameters before const&. So you get move construction into s.
Related
I was reading this post. The code under attention is the following
struct S {
void func() &;
void func() &&;
};
S s1;
s1.func(); // OK, calls S::func() &
S().func(); // OK, calls S::func() &&
I think I understood what are the reference qualifiers. My question is more basic: what is S()? Is it a (copy) creator? Why is it an rvalue? It seems that in that blog and also in other places it is taken for granted. What am I missing?
Formally it's an explicit cast (functional notation). It creates a temporary object from the (possibly empty) list of arguments. And yes, it does so by doing overload resolution to pick the correct constructor to call. In this case, the default c'tor (which your compiler produces, on account of no other c'tor being declared).
More formally, the explicit cast is an expression whose result is a prvalue (pure rvalue). So when doing overload resolution to pick a member function to call, the rvalue qualified version is preferable.
I imagine the blog skimmed over it because that existed in C++ since time immemorial. And the post's intent was to introduce a new concept, assuming readers already know this about C++.
S() creates a temporary object which is an rvalue. The object is constructed using default constructor. It is destroyed just after the full expression is evaluated.
More generally, one way to think about this syntax is: type name + arguments list passed to the constructor of the object.
In this case S is the type name and the empty parenthesis means there are no arguments for the constructor so the default constructor is chosen.
Consider the following:
struct my_type {};
my_type make_my_type() { return my_type{}; }
void func(my_type&& arg) {}
int main()
{
my_type&& ref = make_my_type();
func(ref);
}
Needless to say, this code doesn't compile. I realise that I need to use std::move() in the second function call, but for the purposes of understanding I want to consider the code as it is.
Attempting to compile the above, Clang 3.5 tells me:
error: no matching function for call to 'func'
note: candidate function not viable: no known conversion from 'my_type' to 'my_type &&' for 1st argument void func(my_type&&) {}
While g++ 4.9 says something almost identical:
error: cannot bind 'my_type' lvalue to 'my_type&&'
note: initializing argument 1 of 'void func(my_type&&)'
These error messages have me rather confused, because while ref is certainly an lvalue, its type is still my_type&&... isn't it?
I'm trying to understand exactly what's going on here, so I'm wondering which (if any) of the following are true:
Since only rvalues can be bound to rvalue references, and ref is an lvalue, it cannot be bound to arg. The error messages from both Clang and g++ are misleading in claiming that ref is a (non-reference) my_type that "cannot be converted".
Because it is an lvalue, ref is treated for the purposes of overload resolution as a non-reference my_type, despite its actual type being my_type&&. The error messages from Clang and g++ are misleading because they are displaying the type as used internally for function matching, not the real type of ref.
In the body of main(), the type of ref is plain my_type, despite the fact I explicitly wrote my_type&&. So the error messages from the compilers are accurate, and it is my expectation that is wrong. This doesn't seem to be the case however, since
static_assert(std::is_same<decltype(ref), my_type&&>::value, "");
passes.
There is some other magic going on that I haven't considered.
Just to repeat, I know that the solution is to use std::move() to convert the rref back into an rvalue; I'm looking for an explanation of what's going on "behind the scenes".
Consider these three assignments:
my_type x = func_returning_my_type_byvalue();
my_type & y = func_returning_my_type_byvalue();
my_type && z = func_returning_my_type_byvalue();
The first - you have a local variable x and it's being initialized to the result of a function call (rvalue) so a move constructor/assignment can be used or the construction of x could be elided entirely (skipped over and x is constructed in-place by func_returning_my_type_byvalue when it generates its result).
Note that x is an lvalue - you can take its address, so therefore it is also a type of reference itself. Technically all variables that are not references, are references to themselves. In that respect lvalues are a binding site for assignments to and reads from known-storage-duration memory.
The second will not compile - you cannot assign a reference to a result (this way), you must use reference assignment syntax to alias an existing lvalue. It is perfectly fine to do this, however:
my_type & y = func_returning_my_type_byreference();
// `y` will never use constructors or destructors
This is why the third exists, when we need a reference to something we cannot create a reference to using the conventional syntax. Within something like func in the original question, the lifetime of arg is not immediately obvious. For example we can't do this without an explicit move:
void func( my_type && arg ) {
my_type && save_arg = arg;
}
The reason this is not allowed is because arg is a reference to a value first and foremost. If the storage of arg's value (what it's referring to) were to be shorter than that of save_arg, then save_arg would call the destructor of that value - in effect capturing it. That is not the case here, save_arg will disappear first, so it makes no sense to transfer an lvalue into it that we can, after func, still refer to potentially!
Consider that even if you were to use std:move to force this to compile. The destructor will still not be called within func because you haven't created a new object, just a new reference, and then this reference is destroyed before the original object itself went out of scope.
For all intents and purposes arg behaves as if it's my_type&, as do any rvalue references. The trick is storage duration and the semantics of lifetime extension by reference passing. It's all regular references under the hood, there is no 'rvalue type'.
If it helps, recall the increment / decrement operators. There are two overloads that exist, not two operators. operator++(void) (pre) and operator++(int) (post). There is never an actual int being passed, it's just so the compiler has different signatures for different situations / contexts / agreements about value treatment. This is sort of the same deal with references.
If rvalue and lvalue references are both always referred to like an lvalue, what's the difference?
In a word: object lifetime.
An lvalue reference must always be assigned to using something with longer storage duration, something that is already constructed. That way there is no need to call constructors or destructors for the scope of the lvalue reference variable, because by definition we are given a ready object and forget about it before it's due to be destroyed.
it's also relevant that objects are implicitly destroyed in the reverse order they're defined:
int a; // created first, destroyed last
int b; // created second, destroyed 2nd-last
int & c = b; // fine, `c` goes out of scope before `b` per above
int && d = std::move(a); // fine, `a` outlives `d`, same situation as `c`
If we assigned to an rvalue reference, something that is an lvalue reference, the same rule applies - the lvalue must by definition have longer storage, so we don't need to call constructors or destructors for c or even d. You can't trick the compiler with std::move on this because it knows the scope of the object being moved - d is unambiguously shorter-duration than the reference it's being given, we're just forcing the compiler to use the rvalue type check / context and that's all we've achieved.
The difference is with non-lvalue references - things like expressions where there can be references to them but these references are definitely short-lived, perhaps shorter than the duration of a local variable. Hint Hint.
When we assign the result of a function call or an expression to an rvalue reference, we are creating a reference to a temporary object that otherwise could not be referred to. Due to this, we are in effect forcing in-place construction of a variable from the result of an expression. This is a variation on copy/move elision where the compiler has no choice but to elide the temporary to in-place construction:
int a = 2, b = 3; // lvalues
int && temp = a + b; // temp is constructed in-place using the result of operator+(int,int)
The case with func
It boils down to an lvalue assignment - references as function arguments refer to objects that may exist for longer than a function call, and as such are lvalues even when the argument type is an rvalue reference.
The two cases are:
func( std::move( variable ) ); // case 1
func( my_type() + my_type() ); // case 2
func is not allowed to guess which situation we will use it in ahead of time (sans optimizations). If we didn't allow case 1, then there would be a legitimate reason to consider an rvalue reference parameter as having less storage duration than the function call, but that would also make no sense because either the object is always cleaned up inside func or always outside of it, and having "unknown" storage duration at compile time is not satisfactory.
The compiler has no choice but to assume the worst, that case 1 might happen eventually, in which case we must make guarantees to the storage duration of arg as being longer than the call to func in the general case. As consequence of this - that arg would be considered to exist for longer than the call to func some of the time, and that func's generated code must work in both cases - arg's allowable usage and assumed storage duration meet the requirements of my_type& and not my_type&&.
Under the C++11 standard, is the following pair guaranteed to be moved into the function?
//objects available: key, value
//corresponding type available: pairtype
//function available: void foo(pairtype pair); copies the pair by default
foo({std::move(key),std::move(value)}); //pair moved?
or do I have to do the move myself?
foo(std::move(pairtype(std::move(key),std::move(value))); //needed?
Initializer lists are not expressions, so they do not have a type and they do not yield a value. This means that the following:
{std::move(key),std::move(value)}
Does not in itself create a pair. Initializer lists are just a syntactic construct used for initialization, and in this case the function parameter will be constructed by directly invoking the constructor of pairtype with std::move(key) and std::move(value) as arguments.
There is no creation of temporaries involved - the only thing to be aware of is that explicit constructors will not be considered when performing list-initialization (for instance, this would not work with an instance of std::tuple<>).
Which means that the invocation of foo we just discussed, i.e.:
foo({std::move(key),std::move(value)}
Is technically different from this invocation:
foo(std::move(pairtype(std::move(key),std::move(value)))
Here, you are intentionally creating a temporary and moving it into the function parameter (the compiler may then elide this move per 12.8/31, but this is another story).
Notice, that the call to std::move() here is superfluous, since temporaries are rvalues. The function parameter will be move-constructed from the temporary object anyway. You can therefore write:
foo(pairtype(std::move(key),std::move(value)))
Notice, that pairtype will be an instance of the std::pair<> class template, which means you will have to specify template arguments manually. To avoid this, you can use std::make_pair():
foo(std::make_pair(std::move(key),std::move(value)))
A class must have a valid copy or move constructor for any of this syntax to be legal:
C x = factory();
C y( factory() );
C z{ factory() };
In C++03 it was fairly common to rely on copy elision to prevent the compiler from touching the copy constructor. Every class has a valid copy constructor signature regardless of whether a definition exists.
In C++11 a non-copyable type should define C( C const & ) = delete;, rendering any reference to the function invalid regardless of use (same for non-moveable). (C++11 §8.4.3/2). GCC, for one, will complain when trying to return such an object by value. Copy elision ceases to help.
Fortunately, we also have new syntax to express intent instead of relying on a loophole. The factory function can return a braced-init-list to construct the result temporary in-place:
C factory() {
return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
Edit: If there's any doubt, this return statement is parsed as follows:
6.6.3/2: "A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list."
8.5.4/1: "list-initialization in a copy-initialization context is called copy-list-initialization." ¶3: "if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7)."
Do not be misled by the name copy-list-initialization. 8.5:
13: The form of initialization (using parentheses or =) is generally insignificant, but does matter when the
initializer or the entity being initialized has a class type; see below. If the entity being initialized does not
have class type, the expression-list in a parenthesized initializer shall be a single expression.
14: The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
Both copy-initialization and its alternative, direct-initialization, always defer to list-initialization when the initializer is a braced-init-list. There is no semantic effect in adding the =, which is one reason list-initialization is informally called uniform initialization.
There are differences: direct-initialization may invoke an explicit constructor, unlike copy-initialization. Copy-initialization initializes a temporary and copies it to initialize the object, when converting.
The specification of copy-list-initialization for return { list } statements merely specifies the exact equivalent syntax to be temp T = { list };, where = denotes copy-initialization. It does not immediately imply that a copy constructor is invoked.
-- End edit.
The function result can then be received into an rvalue reference to prevent copying the temporary to a local:
C && x = factory(); // applies to other initialization syntax
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type? The reference trick doesn't work because a reference member doesn't extend the lifetime of a temporary.
Note, I'm not considering aggregate-initialization. This is about defining a constructor.
On your main question:
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type?
You don't.
Your problem is that you are trying to conflate two things: how the return value is generated and how the return value is used at the call site. These two things don't connect to each other. Remember: the definition of a function cannot affect how it is used (in terms of language), since that definition is not necessarily available to the compiler. Therefore, C++ does not allow the way the return value was generated to affect anything (outside of elision, which is an optimization, not a language requirement).
To put it another way, this:
C c = {...};
Is different from this:
C c = [&]() -> C {return {...};}()
You have a function which returns a type by value. It is returning a prvalue expression of type C. If you want to store this value, thus giving it a name, you have exactly two options:
Store it as a const& or &&. This will extend the lifetime of the temporary to the lifetime of the control block. You can't do that with member variables; it can only be done with automatic variables in functions.
Copy/move it into a value. You can do this with a member variable, but it obviously requires the type to be copyable or moveable.
These are the only options C++ makes available to you if you want to store a prvalue expression. So you can either make the type moveable or return a freshly allocated pointer to memory and store that instead of a value.
This limitation is a big part of the reason why moving was created in the first place: to be able to pass things by value and avoid expensive copies. The language couldn't be changed to force elision of return values. So instead, they reduced the cost in many cases.
Issues like this were among the prime motivations for the change in C++17 to allow these initializations (and exclude the copies from the language, not merely as an optimization).
It's stated in [C++11: 12.8/31] :
This elision of copy/move operations, called copy elision, is permitted [...] :
— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
This implies
#include <iostream>
using namespace std;
struct X
{
X() { }
X(const X& other) { cout << "X(const X& other)" << endl; }
};
X no_rvo(X x) {
cout << "no_rvo" << endl;
return x;
}
int main() {
X x_orig;
X x_copy = no_rvo(x_orig);
return 0;
}
will print
X(const X& other)
no_rvo
X(const X& other)
Why is the second copy constructor required? Can't a compiler simply extend the lifetime of x?
Imagine no_rvo is defined in a different file than main so that when compiling main the compiler will only see the declaration
X no_rvo(X x);
and will have no idea whether the object of type X returned has any relation to the argument. From what it knows at that point, the implementation of no_rvo could as well be
X no_rvo(X x) { X other; return other; }
So when it e.g. compiles the line
X const& x = no_rvo(X());
it will do the following, when maximally optimizing.
Generate the temporary X to be passed to no_rvo as argument
call no_rvo, and bind its return value to x
destruct the temporary object it passed to no_rvo.
Now if the return value from no_rvo would be the same object as the object passed to it, then destruction of the temporary object would mean destruction of the returned object. But that would be wrong because the returned object is bound to a reference, therefore extending its lifetime beyond that statement. However simply not destructing the argument is also no solution because that would be wrong if the definition of no_rvo is the alternative implementation I've shown above. So if the function is allowed to reuse an argument as return value, there can arise situations where the compiler could not determine the correct behaviour.
Note that with common implementations, the compiler would not be able to optimize that away anyways, therefore it is not such a big loss that it is not formally allowed. Also note that the compiler is allowed to optimize the copy away anyway if it can prove that this doesn't lead to a change in observable behaviour (the so-called as-if rule).
The usual implementation of RVO is that the calling code passes the address of a memory chunk where the function should construct its result object.
When the function result is directly an automatic variable that is not a formal argument, that that local variable can simply be placed in the caller-provided memory chunk, and the return statement then does no copying at all.
For an argument passed by value the calling machine code has to copy-initialize its actual argument into the formal argument’s location before jumping to the function. For the function to place its result there it would have to destroy the formal argument object first, which has some tricky special cases (e.g., when that construction directly or indirectly refers to the formal argument object). So, instead of identifying the result location with the formal argument location, an optimization here logically has to use a separate called-provided memory chunk for the function result.
However, a function result that is not passed in a register is normally provided by the caller. I.e., what one could reasonably talk about as RVO, a kind of diminished RVO, for the case of a return expression that denotes a formal argument, is what would happen anyway. And it does not fit with the text “by constructing the automatic object directly into the function’s return value”.
Summing up, the data flow requiring that the caller passes in a value, means that it is necessarily the caller that initializes a formal argument's storage, and not the function. Hence, copying back from a formal argument can not be avoided in general (that weasel term covers the special cases where the compiler can do very special things, in particular for inlined machine code). However, it is the function that initializes any other local automatic object’s storage, and then it’s no problem to do RVO.