If I understood correctly, starting from C++17, this code now requires that no copy will be done:
Foo myfunc(void) {
return Foo();
}
auto foo = myfunc(); // no copy
Is it also true for function arguments? Will copies be optimized away in the following code?
Foo myfunc(Foo foo) {
return foo;
}
auto foo = myfunc(Foo()); // will there be copies?
Namely, there had to be an accessible copy and/or move constructor. Guaranteed copy elision redefines a number of C++ concepts, such that certain circumstances where copies/moves could be elided don't actually provoke a copy/move at all. The compiler isn't eliding a copy; the standard says that no such copying could ever happen.
In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".
In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement for details.
The standard allows this copy to be elided by constructing the return value at the call-site rather than in make ( C++11 [class.copy]/31 ). This is called copy elision. The unfortunate part is this: even if all copies of the type are elided, the constructor still must exist. This means that if we instead have:
In C++17, prvalues ("anonymous temporaries") are no longer objects. Instead, they are instructions on how to construct an object.
They can instantiate a temporary from their construction instructions, but as there is no object there, there is no copy/move construction to elide.
Foo myfunc(Foo foo) {
return foo;
}
So here, the function argument foo
is moved into the prvalue return value of myfunc
. You can think of this conceptually as "myfunc
returns instructions on how to make a Foo
". If those instructions are "not used" by your program, a temporary is automatically instantiated and uses those instructions.
auto foo = myfunc(Foo());
So here, Foo()
is a prvalue. It says "construct a Foo
using the ()
constructor". It is then used to construct the argument of myfunc
. No elision occurs, no copy constructor or move constructor is called, just ()
.
Stuff then happens inside myfunc
.
myfunc
returns a prvalue of type Foo
. This prvalue (aka construction instructions) is used to construct the local variable auto foo
.
So what happens here is that a Foo
is constructed via ()
, then moved into auto foo
.
Elision of function arguments into return values is not supported in C++14 nor C++17 as far as I know (I could be wrong, I do not have chapter and verse of the standard here). However, they are implicitly moved when used in a return func_arg;
context.
Yes and no. Quoting cppreference:
Under the following circumstances, the compilers are required to omit the copy- and move- construction of class objects [...]:
In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object
In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.
So, in your second snippet only one default constructor will be called. First, foo
in myFunc
is initialized from Foo()
(1 default construction), which is a prvalue. This means that it will be elided (see point 1).
Next, myFunc
returns a copy of foo
, which cannot be elided as foo
is not a prvalue (point 2). So, one move is made, as foo
is a xvalue. But the actual return value is a prvalue, as it is a new instance of foo
(in myFunc
), and because of point one it is elided.
In conclusion, one default construction and one move is guaranteed by the Standard. There cannot be any more. But, the compiler might actually elide the only move altogether.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With