Copy elision and trivially copyable types - c++

From the standard 6.7.7 (temporary objects), we can see:
When an object of class type X is passed to or returned from a function, if X has at least one eligible copy or move constructor ([special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, 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 eligible 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 4: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
Does it mean that copy elision is not mandatory with trivially copyable types? What I understand here is that if we declare a destructor like ~Object() {} instead of not declaring anything (so the destructor would be generated by the compiler) or a default one, the object becomes not trivially constructible, and so, the copy elision must be performed (at the condition we respect the well-known condition for copy elision to occur).

Does it mean that copy elision is not mandatory with trivially copyable types?
In principle, yes, but the goal of the section you quoted was not to exempt POD types from copy elision but to bypass ABI restrictions on how objects are passed in function calls. It allows POD objects to be passed via registers. This is the best the standard can do given the C++ abstract machine knows nothing about the physical machine and its registers and calling conventions.
Guaranteed copy elision is a result of several changes spread throughout the standard, which includes deferred prvalue materialization. For details refer to the original proposal p0135r1.
With those changes it becomes possible (and required) to initialize an object without involving temporaries ([dcl.init.general]/15.6.1):
— Otherwise, if the destination type is a (possibly
cv-qualified) class type:
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.

Related

Does static_cast<T>(funcReturningT()) inhibit RVO?

C++17 guarantees copy elision for:
T funcReturningT() {
return T(...);
}
T t=funcReturningT();
Now if I wrap the return into a static_cast to the same type, like so:
T t=static_cast<T>(funcReturningT());
does the standard still guarantee copy elision or not?
RVO is dead. Long live RVO.
In modern C++, there are two cases corresponding to what used to be called RVO:
There's NRVO, in which the compiler is allowed to elide a local variable if it can see that that variable will just be returned eventually. If the compiler declines to elide the local variable, then it is obligated to treat it as an rvalue when it is returned, assuming certain conditions are met (thus converting a copy into a move). This is not what your question is asking about.
There's also guaranteed copy elision --- but to even use that name for it is to think about C++17 using a pre-C++17 mindset. What really changed in C++17 is that prvalues are no longer "objects without identity" but rather "recipes for creating objects". This is the reason why copy-elision-like behaviour is guaranteed.
So let's look at your statement:
T t=static_cast<T>(funcReturningT());
The expression funcReturningT() is a prvalue. In C++14, this would mean that immediately upon evaluating it, the implementation would have to instantiate a temporary T object (lacking identity), but non-guaranteed copy elision would allow the compiler (at its discretion) to elide such object. In C++17, it is ready to create a T object but doesn't do so immediately.
Then the static_cast is evaluated, and the result of it is also a prvalue of the same type. Because of this, no move constructor is required and no temporary object needs to be created. The result of the cast is just the original "recipe".
And finally, when t is initialized from the result of the static_cast, the move constructor is once again not required. The "recipe" that static_cast used is simply executed with t as the object that it creates.
The beauty of it is that there is nothing to elide, which is why "guaranteed copy elision" is a misnomer.
(Sometimes, temporary objects do need to be created: in particular, if any constructor or conversion function needs to be called at any point, then the prvalue has to be "materialized" in order for that call to actually have an object to work with. In your example, this is not necessary.)

Return by value copies instead of moving

Why does this program call the copy constructor instead of the move constructor?
class Qwe {
public:
int x=0;
Qwe(int x) : x(x){}
Qwe(const Qwe& q) {
cout<<"copy ctor\n";
}
Qwe(Qwe&& q) {
cout<<"move ctor\n";
}
};
Qwe foo(int x) {
Qwe q=42;
Qwe e=32;
cout<<"return!!!\n";
return q.x > x ? q : e;
}
int main(void)
{
Qwe r = foo(50);
}
The result is:
return!!!
copy ctor
return q.x > x ? q : e; is used to disable nrvo. When I wrap it in std::move, it is indeed moved. But in "A Tour of C++" the author said that the move c'tor must be called when it available.
What have I done wrong?
You did not write your function in a way that allows copy/move elision to occur. The requirements for a copy to be replaced by a move are as follows:
[class.copy.elision]/3:
In the following copy-initialization contexts, a move operation might
be used instead of a copy operation:
If the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic
storage duration declared in the body or
parameter-declaration-clause of the innermost enclosing function or lambda-expression
overload resolution to select the constructor for the copy is first
performed as if the object were designated by an rvalue. If the first
overload resolution fails or was not performed, 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.
The above is from C++17, but the C++11 wording is pretty much the same. The conditional operator is not an id-expression that names an object in the scope of the function.
An id-expression would be something like q or e in your particular case. You need to name an object in that scope. A conditional expression doesn't qualify as naming an object, so it must preform a copy.
If you want to exercise your English comprehension abilities on a difficult wall of text, then this is how it's written in C++11. Takes some effort to see IMO, but it's the same as the clarified version above:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. [...]
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
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.
StoryTeller didn't answer the question: Why is the move c'tor not called? (And not: Why is there no copy elision?)
Here's my go: The move c'tor will be called if and only if:
Copy elision (RVO) is not performed. Your use of the ternary operator is indeed a way to prevent copy elision. Let me point out though that return (0, q); is a simpler way to do this if you just want to return q while suppressing copy elision. This uses the (in-)famous comma operator. Possibly return ((q)); might work, too, but I am not enough of a language lawyer to tell for sure.
The argument to return is an rvalue. This could be a temporary (more precisely, a prvalue), but these are also eligible for copy elision. Therefore, the argument to return must be an xvalue, such as std::move(q) if you want to ensure the move c'tor is called.
See also: C++ value categories
Some more technicalities of your example:
q and e are objects of type Qwe.
q.x > x ? q : e is an lvalue expression of type Qwe. This is because the expressions q and e are lvalues of type Qwe. The ternary operator just selects either of them.
std::move(q.x > x ? q : e) is an xvalue expression of type Qwe. The std::move simply turns (casts) the lvalue into an xvalue. As an aside, q.x > x ? std::move(q) : std::move(e) would also work.
The copy c'tor gets called in return q.x > x ? q : e; because it can be called with an lvalue of type Qwe (constness is optional), while, on the other hand, the move c'tor cannot be called with an lvalue and is therefore eliminated from the candidate set.
UPDATE: Addressing the comments by going into more depth… this is a really confusing aspect of C++!
Conceptually, in C++98, returning an object by value meant returning a copy of the object, so the copy c'tor would be called. However, the standard's authors considered that a compiler should be free to perform an optimization such that this potentially expensive copy (e.g. of a container) could be elided under suitable circumstances.
This copy elision means that, instead of creating the object in one place and then copying it to a memory address controlled by the caller, the callee creates the object directly in the memory controlled by the caller. Therefore, only the "normal" constructor, e.g. a default c'tor, is called.
Therefore, they added a passage such that the compiler is required to check that the copy c'tor — whether generated or user-defined – exists and is accessible (there was no notion yet of deleted functions for that matter), and must ensure that the object is initialized as-if it had been first created in a different place and then copied (cf. as-if rule), but the compiler was not required to ensure that any side effects of the copy c'tor would be observable, such as the stream output in your example.
The reason why the c'tor was still required to be there was that they wanted to avoid a scenario where a compiler was able to accept code that another would have to reject, simply because the former implemented an optional optimization that the latter did not.
In C++11, move semantics were added, and the committee very much wanted to use this in such a manner that a lot of existing return-by-value functions e.g. involving strings or containers would become more efficient. This was done in such a way that conditions were given under which the compiler was actually required to perform a move instead of a copy. However, the idea of copy elision remained important, so basically there were now four different categories:
The compiler is required to check for a usable (see above) move c'tor, but is allowed to elide it.
The compiler is required to check for a usable move c'tor, and has to call it.
The compiler is required to check for a usable copy c'tor, but is allowed to elide it.
The compiler is required to check for a usable copy c'tor, and has to call it.
… which in turn lead to four possible outcomes:
Compiler checks for move c'tor, but then elides it. (relates to 1. above)
Compiler checks for move c'tor and actually emits a call to it. (relates to 1. or 2. above)
Compiler checks for copy c'tor, but then elides it. (relates to 3. above)
Compiler checks for copy c'tor and actually emits a call to it. (relates to 3. or 4. above)
And the long optimization story doesn't end here, because, in C++17, the compiler is required to elide certain c'tor calls. In these cases, the compiler is not even allowed to demand that a copy or move c'tor is available.
Note that a compiler has always been free to elide even such c'tor calls that do not meet the standard requirements, under the protection of the as-if rule, for instance by function inlining and the following optimization steps. Anyway, a function call, conceptually, does not have to be backed by the actual machine instruction for the execution of a subroutine. The compiler is just not allowed to remove observable, otherwise defined behavior.
By now you should have noticed that, at least prior to C++17, it is very well possible for the same well-formed program to behave differently, depending on the compiler used and even optimization settings, if the copy rsp. move constructor has observable side effects. Also, a compiler that implements copy/move elision may do so for a subset of the conditions under which the standard allows it to happen. This makes your question almost impossible to answer in detail. Why is the copy/move c'tor called here, but not there? Well, it may be because of the requirements of the C++ standard, but it also may be the preference of your compiler. Maybe the compiler authors had time and leisure implementing the one optimization but not the other. Maybe they found it too difficult in the latter case. Maybe they just had more important stuff to do. Who knows?
What matters 99% of the time for me as a developer is to write my code in such a way that the compiler can apply the best optimizations. Sticking to common cases and standard practice is one thing. Knowing NRVO and RVO of temporaries is another thing, and writing the code such that the standard allows (or, in C++17, requires) copy/move elision, and ensuring that a move c'tor is available where it is beneficial (in case elision does not occur). Don't rely on side effects such as writing a log message or incrementing a global counter. These are not what a copy or move c'tor should commonly do anyway, except possibly for debugging or scholarly interest.

Will any compiler actually ever elide these copies?

Given
struct Range{
Range(double from, double to) : from(from), to(to) {}
double from;
double to;
// if it matters to the compiler, we can add more fields here to make copying expensive
};
struct Box{
Box(Range x, Range y) : x(x), y(y) {}
Range x;
Range y;
};
someone said that in Box box(Range(0.0,1.0),Range(0.0,2.0)), the compiler can avoid copying Range objects altogether by constructing them inside box to begin with.
Does any compiler actually do this?
My own attempts haven't succeeded.
The compiler can - and normally does - elide the copies from the temporary to the argument. The compiler cannot elide the copy from the argument to members. While it may technically possible to elide these copies in some cases, the relevant permission isn't given. The section of the standard is 12.8 [class.copy] paragraph 31 which spells out 4 situations where a copy can be elided (the exact rules are a bit non-trivial):
When returning a named, function local variable using it name.
When using a named, function local variable in a throw expression.
When copying a temporary object.
When catching an exception by value.
Passing a named argument as parameter to the construction of a member variable is, clearly, none of these situations.
The essential background of the rules for copy elision is that in some contexts the declaration of functions suffice to determine when an object will be used. If it is clear upon construction time where the object can be constructed, it can be elided. The caller of a constructor cannot determine based only on the declaration of the constructor where the object will be used.
That Someone is me. So let me clear my stand.
I never said that in Box box(Range(0.0,1.0),Range(0.0,2.0)), the compiler can avoid copying Range objects altogether by constructing them inside box to begin with. What I said was:
Yes it can, In particular this kind of copy elision context falls under the copy elision criterion specified in 12.8/p31.3 Copying and moving class objects [class.copy] of the standard:
(31.3) -- when a temporary class object that has not been bound to a reference
(12.2) would be copied/moved to a class object with the same type
(ignoring cv-qualification), the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move.
The Yes it can, part goes for the temporary objects passed in the constructor (That can be elide per standard as mentioned above). I never said that the parameters can be elided all the way to the Box constructor's initializer list.
After all, that case doesn't qualify for any of the criteria where copy elision can be applied as per standard.
I also said that even if a certain context is qualified as a context where copy elision can be applied, the compiler is not obligated to follow. If you rely on that effects then your program is not considered portable.

Why is my copy constructor only called twice in this scenario?

I have the following two functions:
Class foo(Class arg)
{
return arg;
}
Class bar(Class *arg)
{
return *arg;
}
Now, when I solely call foo(arg), the copy constructor is of course called twice. When I call bar(&arg) solely, it's only called once. Thus, I would expect
foo(bar(&arg));
the copy constructor being called three times here. However, it's still only called twice. Why is that? Does the compiler recognise that another copy is unneeded?
Thanks in advance!
Does the compiler recognise that another copy is unneeded?
Indeed it does. The compiler is performing copy/move elision. That is the only exception to the so called "as-if" rule, and it allows the compiler (under some circumstances, like the one in your example) to elide calls to the copy or move constructor of a class even if those have side effects.
Per paragraph 12.8/31 of the C++11 Standard:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
object, even if the constructor selected for the copy/move operation and/or the destructor for the object
have side effects. In such cases, the implementation treats the source and target of the omitted copy/move
operation as simply two different ways of referring to the same object, and the destruction of that object
occurs at the later of the times when the two objects would have been destroyed without the optimization.
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
— [...]
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the omitted copy/move
— [...]
With GCC you can try using the -fno-elide-constructor compilation flag to suppress this optimization and see how the compiler would behave when no copy elision occurs.

Is an object guaranteed to be moved when it is returned?

I know that when passing an object by value to a function, the move constructor is always called if there is one, assuming no copy elision. What about returning an object by value?
For example, say we have a class Foo which has a move constructor, and we have a function that returns a Foo object.
Foo g() {
Foo f;
// do something with f
return f;
}
If we assume there is no RVO, is the move constructor guaranteed to be called?
Update: I guess I didn't show my intention clearly. I just want to know I can in the worst case have the object moved not copied. Either RVO or NRVO happens, I am happy. And I should also say that move constructor and move assignment are not deleted and are properly implemented.
Yes. See [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 ]
In this case, since the return value has a name (f), it would be NRVO (named return value optimization) that would apply.
So, the technical answer based only on wording, is that the absence of RVO won't prevent copy elision, since NRVO could still allow it.
Past that, I believe the selection between move/copy of the return value can/will depend on the definition of Foo -- there are definitely times it'll be copied instead of moved, such as if you've explicitly deleted the move constructor and move assignment operators, or you haven't defined move construction/assignment, and it isn't eligible for them being synthesized implicitly.
Edit: [responding to edited question]: Having a move constructor still doesn't guarantee that the result will be moved. One obvious example would be if you had deleted the move assignment operator, and were assigning the result (rather than using it to initialize). In this case, the deleted move assignment operator would prevent moving the return value.
To answer what you may have been getting at, though, the general rule is that moving will be done if possible, and it'll fall back to copying if and only if something prevents the result from being moved.
The rule is that whenever copy elision is allowed but does not occur, the move constructor will be used if it is available, and otherwise the copy constructor will be used.
The exact behaviour is defined by [class.copy]/32:
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.