Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is copy/move elision allowed to make a program using deleted functions well-formed?

Consider the following code:

#include <iostream>

struct Thing
{
    Thing(void)                       {std::cout << __PRETTY_FUNCTION__ << std::endl;}
    Thing(Thing const &)              = delete;
    Thing(Thing &&)                   = delete;
    Thing & operator =(Thing const &) = delete;
    Thing & operator =(Thing &&)      = delete;
};

int main()
{
    Thing thing{Thing{}};
}

I expect Thing thing{Thing{}}; statement to mean construction of temporary object of Thing class using default constructor and construction of thing object of Thing class using move constructor with just created temporary object as an argument. And I expect that this program to be considered ill-formed because it contains an invocation of deleted move constructor, even though it can be potentially elided. The class.copy.elision section of standard seems to demand this as well:

the selected constructor must be accessible even if the call is elided

The Wording for guaranteed copy elision through simplified value categories does not seem to allow it either.

However gcc 7.2 (and clang 4 as well, but not VS2017 which still does not support guaranteed copy elision) will compile this code just fine eliding move constructor call.

Which behavior would be correct in this case?

like image 525
user7860670 Avatar asked Sep 06 '17 09:09

user7860670


People also ask

What is copy move elision?

C++ Copy Elision Purpose of copy elision Copy elision (sometimes called return value optimization) is an optimization whereby, under certain specific circumstances, a compiler is permitted to avoid the copy or move even though the standard says that it must happen.

Is copy elision guaranteed?

You can see this behavior in compiler versions Visual Studio 2017 15.6, Clang 4, GCC 7, and above. Despite the name of the paper and what you might read on the Internet, the new rules do not guarantee copy elision. Instead, the new value category rules are defined such that no copy exists in the first place.

What is RVO in cpp?

In the context of the C++ programming language, return value optimization (RVO) is a compiler optimization that involves eliminating the temporary object created to hold a function's return value. RVO is allowed to change the observable behaviour of the resulting program by the C++ standard.

What is copy elision in compiler?

Copy elision (or Copy omission) is a compiler optimization technique that avoids unnecessary copying of objects. Now a days, almost every compiler uses it. Let us understand it with the help of an example. The output of above program is: Why copy constructor is not called?

Does move on a temporary object prevent copy elision?

The hint is still correct though, since std::move on a temporary either doesn't have any effect at all (as here) or prevents elision if used in a context where copy elision would otherwise be allowed/mandatory, for example if this was the initializer of m_thread instead of an assignment to it.

Why is copy elision disabled in C++?

This is disabled if such copy elision would change the observable behavior of the program for any reason other than skipping the copy constructor and the destructor of the catch clause argument (for example, if the catch clause argument is modified, and the exception object is rethrown with throw ).

Why can’t I use copy elision in debug mode?

Because some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.


1 Answers

It doesn't make an ill-formed program build. It gets rid of the reference to the deleted function entirely. The appropriate wording in the proposal is here:

[dcl.init] bullet 17.6

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. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. ]

The example further strengthens this. Since it indicates the whole expression must collapse into a single default construction.

The thing to note is that the deleted function is never odr-used when the copies are elided due to value categories, so the program is not referring to it.

This is an important distinction, since the other form of copy elision still odr-uses the copy c'tor, as described here:

[basic.def.odr]/3

... A constructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation ([class.copy] ...

[class.copy] describes the other form of permissible (but not mandatory) copy-elision. Which, if we demonstrate with your class:

Thing foo() {
    Thing t;
    return t; // Can be elided according to [class.copy.elision] still odr-used
}

Should make the program ill-formed. And GCC complains about it as expected.


And by the way. If you think the previous example in the online compiler is a magicians trick, and GCC complains because it needs to call the move c'tor. Have a look at what happens when we supply a definition.

like image 59
StoryTeller - Unslander Monica Avatar answered Nov 02 '22 05:11

StoryTeller - Unslander Monica