Does guaranteed copy elision work with function parameters? - c++

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?

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.
(The instructions, by the way, include time travel provisions; the time-point of construction remains inside the function itself!)
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.

Related

Does an unmaterialized temporary needs the destructor to be accessible?

GCC 7.2 and Clang 5.0 do not agree in this case:
struct A;
A foo();
struct A
{
static void bar()
{
foo();
}
private:
~A()=default;
};
A foo()
{
return {};
//GCC error: A::~A() is private in this context.
};
This behavior is part of the "c++17 guaranteed (copy elision and is not related RVO or NRVO)."
GCC does not compile this code but Clang does. Which one is wrong?
Maybe this paragraph says that the bot Clang and GCC are standard compliant [class.temporary]:
When an object of class type X is passed to or returned from a function, if each copy constructor, move
constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move
constructor, implementations are permitted to create a temporary object to hold the function parameter or
result object. The temporary object is constructed from the function argument or return value, respectively,
and the function’s parameter or return object is initialized as if by using the non-deleted trivial constructor
to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution
to perform a copy or move of the object). [ Note: This latitude is granted to allow objects of class type to be
passed to or returned from functions in registers. — end note ]
I believe this is a clang bug.
From [class.temporary]:
Temporary objects are created [...] when needed by the implementation to pass or return an object of trivially-copyable type (see below), and [...]
Even when the creation of the temporary object is unevaluated ([expr.prop]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed. [ Note: This includes accessibility and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of the operand of a decltype-specifier ([expr.call]), no temporary is introduced, so the foregoing does not apply to such a prvalue. — end note ]
The copy-initialization of the return of foo is a context that creates a temporary object, so the semantic restrictions must still be followed - which include the accessibility of the destructor (as the note helps make clear). ~A() must be accessible in this context, and isn't, so the program should be ill-formed. gcc is correct to reject.
I believe this is a gcc bug in C++17.
According to copy elision:
since C++17
Under the following circumstances, the compilers are required
to omit the copy- and move- construction of class objects even
if the copy/move constructor and the destructor have
observable side-effects. They need not be present or accessible,
as the language rules ensure that no copy/move operation takes
place, even conceptually:
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.
Your function foo returns a prvalue object of type A and the return
type of foo is A, no matter whether A has accessible copy/move
constructor and destructor, the copy will be omitted in C++17.

Can't return unique_ptr element from an array by value [duplicate]

unique_ptr<T> does not allow copy construction, instead it supports move semantics. Yet, I can return a unique_ptr<T> from a function and assign the returned value to a variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
The code above compiles and works as intended. So how is it that line 1 doesn't invoke the copy constructor and result in compiler errors? If I had to use line 2 instead it'd make sense (using line 2 works as well, but we're not required to do so).
I know C++0x allows this exception to unique_ptr since the return value is a temporary object that will be destroyed as soon as the function exits, thus guaranteeing the uniqueness of the returned pointer. I'm curious about how this is implemented, is it special cased in the compiler or is there some other clause in the language specification that this exploits?
is there some other clause in the language specification that this exploits?
Yes, see 12.8 §34 and §35:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object [...]
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 with the same cv-unqualified type as the function return type [...]
When the criteria for elision of a copy operation are met and the object to be copied is designated by an lvalue,
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Just wanted to add one more point that returning by value should be the default choice here because a named value in the return statement in the worst case, i.e. without elisions in C++11, C++14 and C++17 is treated as an rvalue. So for example the following function compiles with the -fno-elide-constructors flag
std::unique_ptr<int> get_unique() {
auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
return ptr; // <- 2, moved into the to be returned unique_ptr
}
...
auto int_uptr = get_unique(); // <- 3
With the flag set on compilation there are two moves (1 and 2) happening in this function and then one move later on (3).
This is in no way specific to std::unique_ptr, but applies to any class that is movable. It's guaranteed by the language rules since you are returning by value. The compiler tries to elide copies, invokes a move constructor if it can't remove copies, calls a copy constructor if it can't move, and fails to compile if it can't copy.
If you had a function that accepts std::unique_ptr as an argument you wouldn't be able to pass p to it. You would have to explicitly invoke move constructor, but in this case you shouldn't use variable p after the call to bar().
void bar(std::unique_ptr<int> p)
{
// ...
}
int main()
{
unique_ptr<int> p = foo();
bar(p); // error, can't implicitly invoke move constructor on lvalue
bar(std::move(p)); // OK but don't use p afterwards
return 0;
}
unique_ptr doesn't have the traditional copy constructor. Instead it has a "move constructor" that uses rvalue references:
unique_ptr::unique_ptr(unique_ptr && src);
An rvalue reference (the double ampersand) will only bind to an rvalue. That's why you get an error when you try to pass an lvalue unique_ptr to a function. On the other hand, a value that is returned from a function is treated as an rvalue, so the move constructor is called automatically.
By the way, this will work correctly:
bar(unique_ptr<int>(new int(44));
The temporary unique_ptr here is an rvalue.
I think it's perfectly explained in item 25 of Scott Meyers' Effective Modern C++. Here's an excerpt:
The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue. In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::move is implicitly applied to local objects being returned.
Here, RVO refers to return value optimization, and if the conditions for the RVO are met means returning the local object declared inside the function that you would expect to do the RVO, which is also nicely explained in item 25 of his book by referring to the standard (here the local object includes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or std::move is implicitly applied to local objects being returned. Scott mentions in item 25 that std::move is implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.
In your case, the code is clearly a candidate for RVO as it returns the local object p and the type of p is the same as the return type, which results in copy elision. And if the compiler chooses not to elide the copy, for whatever reason, std::move would've kicked in to line 1.
One thing that i didn't see in other answers is To clarify another answers that there is a difference between returning std::unique_ptr that has been created within a function, and one that has been given to that function.
The example could be like this:
class Test
{int i;};
std::unique_ptr<Test> foo1()
{
std::unique_ptr<Test> res(new Test);
return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
// return t; // this will produce an error!
return std::move(t);
}
//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
I would like to mention one case where you must use std::move() otherwise it will give an error.
Case: If the return type of the function differs from the type of the local variable.
class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
std::unique_ptr<Derived> derived(new Derived());
return std::move(derived); //std::move() must
}
Reference: https://www.chromium.org/developers/smart-pointer-guidelines
I know it's an old question, but I think an important and clear reference is missing here.
From https://en.cppreference.com/w/cpp/language/copy_elision :
(Since C++11) 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.

Is a rvalue reference parameter that is returned by value an xvalue?

My understanding is that, in the following function, the expression foo in the statement return foo; is an xvalue, because the object it denotes is expiring (even though foo is an lvalue in previous statements):
Foo bar()
{
Foo foo;
change(foo);
return foo;
}
Such an expiring value is not covered by What expressions create xvalues?.
Does that change in the following case?
Foo bar(Foo&& foo)
{
change(foo);
return foo;
}
Is foo an xvalue in the return statement? And in particular, is it a candidate for move? And for RVO? Or should one use return std::move(foo)?
I do not know what the formal rule is for classifying the expression foo as an xvalue in the return statement of the first case, so I cannot test it in the second.
In that function, foo is an lvalue of type "rvalue reference to Foo". When you return it, since a copy has to be constructed (due to the type of the returned value), you are constructing a whole new value, which makes bar(...) an prvalue, as per §3.10.1.5:
A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a reference is a prvalue. The value of a literal such as 12, 7.3e5, or true is also a prvalue. — end example ]
Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected.
And yes, RVO applies here, assuming a move is not selected first. There's nothing special in that regard here. As per §12.8.31:
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
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
[...]
To clarify, foo per se is an lvalue, but the statement:
return foo;
ultimately results (from the bar(...) expression) in a prvalue due to the fact that given that return type, the expression is equivalent to:
return Foo(foo);
which means that a temporary value, copied from foo is returned from the function bar.
Rereading the reply, it still does not make sense to me. You say Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected. Why is this true in one case and not the other?
When returning foo you have to create a new Foo value (because you are returning a copy) from the lvalue reference foo. This is done implicitly by the copy constructor. So return foo; is equivalent to return Foo(foo). Given that foo is an lvalue, the copy constructor is selected (and not the move constructor).
Now, when you have this new temporary value (constructed from foo), the value itself, which comes out of the expression bar(...), is a prvalue. So when you do:
auto res = bar(...);
You have to construct a Foo copy out of a prvalue. Since a prvalue is also an rvalue, the constructor with the rvalue reference parameter (move constructor) is selected.

C++11: Move/Copy construction ambiguity?

In C++11 we can define copy and move constructors, but are both allowed on the same class? If so, how do you disambiguate their usage? For example:
Foo MoveAFoo() {
Foo f;
return f;
}
Is the above a copy? A move? How do I know?
Usually it will be neither due to RVO.
If that optimisation can't be performed, then it will be a move, because the object being returned is going out of scope (and will be destroyed just after). If it can't be moved, then it will be copied. If it can't be copied, it won't compile.
The whole point of move constructors is that when a copy is going to be made of an object that is just about to be destroyed, it is often unnecessary to make a whole copy, and the resources can be moved from the dying object to the object being created instead.
You can tell when either the copy or move constructor is going to be called based on what is about to happen to the object being moved/copied. Is it about to go out of scope and be destructed? If so, the move constructor will be called. If not, the copy constructor.
Naturally, this means you may have both a move constructor and copy constructor in the same class. You can also have a copy assignment operator and a move assignment operator as well.
Update: It may be unclear as to exactly when the move constructor/assignment operator is called versus the plain copy constructor/assignment operator. If I understand correctly, the move constructor is called if an object is initialised with an xvalue (eXpiring value). §3.10.1 of the standard says
An xvalue (an “eXpiring” value) also refers to an object, usually near
the end of its lifetime (so that its resources may be moved, for
example). An xvalue is the result of certain kinds of expressions
involving rvalue references (8.3.2). [ Example: The result of calling
a function whose return type is an rvalue reference is an xvalue. —end
example ]
And the beginning of §5 of the standard says:
[ Note: An expression is an xvalue if it is:
the result of calling a
function, whether implicitly or explicitly, whose return type is an
rvalue reference to object type,
a cast to an rvalue reference to
object type,
a class member access expression designating a
non-static data member of non-reference type in which the object
expression is an xvalue, or
a .* pointer-to-member expression in
which the first operand is an xvalue and the second operand is a
pointer to data member.
In general, the effect of this rule is that
named rvalue references are treated as lvalues and unnamed rvalue
references to objects are treated as xvalues; rvalue references to
functions are treated as lvalues whether named or not. —end note ]
As an example, if NRVO can be done, it's like this:
void MoveAFoo(Foo* f) {
new (f) Foo;
}
Foo myfoo; // pretend this isn't default constructed
MoveAFoo(&myfoo);
If NRVO can't be done but Foo is moveable, then your example is a little like this:
void MoveAFoo(Foo* fparam) {
Foo f;
new (fparam) Foo(std::move(f));
}
Foo f; // pretend this isn't being default constructed
MoveAFoo(&f);
And if it can't be moved but it can be copied, then it's like this
void MoveAFoo(Foo* fparam) {
Foo f;
new (fparam) Foo((Foo&)f);
}
Foo f; // pretend this isn't default constructed
MoveAFoo(&f);
To back up #Seth, here's the relevant paragraph from the standard:
§12.8 [class.copy] p32
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]
The "disambiguation" is just your old friend, overload resolution:
Foo y;
Foo x(y); // copy
Foo x(std::move(y)); // move
The expression y in the first example is an lvalue of type Foo, which binds to Foo const & (and also Foo & if you have such a constructor); the type of the expression std::move(y) in the second example is Foo &&, so it'll bind to Foo && (and also Foo const & absent the former).
In your example, the result of MoveAFoo() is a temporary of type Foo, so it'll bind to the Foo &&-constructor if one is available, and to a const-copy constructor otherwise.
Finally, in a function returning Foo (by value), the statement return x; is equivalent to return std::move(x); if x is a local variable of type Foo -- this is a special new rule to make the use of move semantics easier.
Foo MoveAFoo() {
Foo f;
return f;
}
This is definition of function MoveAFoo that returns object of type Foo. In its body local Foo f; is created and destructed when it goes out of its scope.
In this code:
Foo x = MoveAFoo();
object Foo f; is created inside of MoveAFoo function and directly assigned into x, which means that copy constructor is not called.
But in this code:
Foo x;
x = MoveAFoo();
object Foo f; is created inside of MoveAFoo function, then the copy of f is created and stored into x and original f is destructed.

Returning unique_ptr from functions

unique_ptr<T> does not allow copy construction, instead it supports move semantics. Yet, I can return a unique_ptr<T> from a function and assign the returned value to a variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
The code above compiles and works as intended. So how is it that line 1 doesn't invoke the copy constructor and result in compiler errors? If I had to use line 2 instead it'd make sense (using line 2 works as well, but we're not required to do so).
I know C++0x allows this exception to unique_ptr since the return value is a temporary object that will be destroyed as soon as the function exits, thus guaranteeing the uniqueness of the returned pointer. I'm curious about how this is implemented, is it special cased in the compiler or is there some other clause in the language specification that this exploits?
is there some other clause in the language specification that this exploits?
Yes, see 12.8 §34 and §35:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object [...]
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 with the same cv-unqualified type as the function return type [...]
When the criteria for elision of a copy operation are met and the object to be copied is designated by an lvalue,
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Just wanted to add one more point that returning by value should be the default choice here because a named value in the return statement in the worst case, i.e. without elisions in C++11, C++14 and C++17 is treated as an rvalue. So for example the following function compiles with the -fno-elide-constructors flag
std::unique_ptr<int> get_unique() {
auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
return ptr; // <- 2, moved into the to be returned unique_ptr
}
...
auto int_uptr = get_unique(); // <- 3
With the flag set on compilation there are two moves (1 and 2) happening in this function and then one move later on (3).
This is in no way specific to std::unique_ptr, but applies to any class that is movable. It's guaranteed by the language rules since you are returning by value. The compiler tries to elide copies, invokes a move constructor if it can't remove copies, calls a copy constructor if it can't move, and fails to compile if it can't copy.
If you had a function that accepts std::unique_ptr as an argument you wouldn't be able to pass p to it. You would have to explicitly invoke move constructor, but in this case you shouldn't use variable p after the call to bar().
void bar(std::unique_ptr<int> p)
{
// ...
}
int main()
{
unique_ptr<int> p = foo();
bar(p); // error, can't implicitly invoke move constructor on lvalue
bar(std::move(p)); // OK but don't use p afterwards
return 0;
}
unique_ptr doesn't have the traditional copy constructor. Instead it has a "move constructor" that uses rvalue references:
unique_ptr::unique_ptr(unique_ptr && src);
An rvalue reference (the double ampersand) will only bind to an rvalue. That's why you get an error when you try to pass an lvalue unique_ptr to a function. On the other hand, a value that is returned from a function is treated as an rvalue, so the move constructor is called automatically.
By the way, this will work correctly:
bar(unique_ptr<int>(new int(44));
The temporary unique_ptr here is an rvalue.
I think it's perfectly explained in item 25 of Scott Meyers' Effective Modern C++. Here's an excerpt:
The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue. In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::move is implicitly applied to local objects being returned.
Here, RVO refers to return value optimization, and if the conditions for the RVO are met means returning the local object declared inside the function that you would expect to do the RVO, which is also nicely explained in item 25 of his book by referring to the standard (here the local object includes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or std::move is implicitly applied to local objects being returned. Scott mentions in item 25 that std::move is implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.
In your case, the code is clearly a candidate for RVO as it returns the local object p and the type of p is the same as the return type, which results in copy elision. And if the compiler chooses not to elide the copy, for whatever reason, std::move would've kicked in to line 1.
One thing that i didn't see in other answers is To clarify another answers that there is a difference between returning std::unique_ptr that has been created within a function, and one that has been given to that function.
The example could be like this:
class Test
{int i;};
std::unique_ptr<Test> foo1()
{
std::unique_ptr<Test> res(new Test);
return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
// return t; // this will produce an error!
return std::move(t);
}
//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
I would like to mention one case where you must use std::move() otherwise it will give an error.
Case: If the return type of the function differs from the type of the local variable.
class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
std::unique_ptr<Derived> derived(new Derived());
return std::move(derived); //std::move() must
}
Reference: https://www.chromium.org/developers/smart-pointer-guidelines
I know it's an old question, but I think an important and clear reference is missing here.
From https://en.cppreference.com/w/cpp/language/copy_elision :
(Since C++11) 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.