Guaranteed copy elision

suggest change

Normally, elision is an optimization. While virtually every compiler support copy elision in the simplest of cases, having elision still places a particular burden on users. Namely, the type who’s copy/move is being elided must still have the copy/move operation that was elided.

For example:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  return std::lock_guard<std::mutex>(a_mutex);
}

This might be useful in cases where a_mutex is a mutex that is privately held by some system, yet an external user might want to have a scoped lock to it.

This is also not legal, because std::lock_guard cannot be copied or moved. Even though virtually every C++ compiler will elide the copy/move, the standard still requires the type to have that operation available.

Until C++17.

C++17 mandates elision by effectively redefining the very meaning of certain expressions so that no copy/moving takes place. Consider the above code.

Under pre-C++17 wording, that code says to create a temporary and then use the temporary to copy/move into the return value, but the temporary copy can be elided. Under C++17 wording, that does not create a temporary at all.

In C++17, any prvalue expression, when used to initialize an object of the same type as the expression, does not generate a temporary. The expression directly initializes that object. If you return a prvalue of the same type as the return value, then the type need not have a copy/move constructor. And therefore, under C++17 rules, the above code can work.

The C++17 wording works in cases where the prvalue’s type matches the type being initialized. So given get_lock above, this will also not require a copy/move:

std::lock_guard the_lock = get_lock();

Since the result of get_lock is a prvalue expression being used to initialize an object of the same type, no copying or moving will happen. That expression never creates a temporary; it is used to directly initialize the_lock. There is no elision because there is no copy/move to be elided elide.

The term “guaranteed copy elision” is therefore something of a misnomer, but that is the name of the feature as it is proposed for C++17 standardization. It does not guarantee elision at all; it eliminates the copy/move altogether, redefining C++ so that there never was a copy/move to be elided.

This feature only works in cases involving a prvalue expression. As such, this uses the usual elision rules:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  std::lock_guard<std::mutex> my_lock(a_mutex);
  //Do stuff
  return my_lock;
}

While this is a valid case for copy elision, C++17 rules do not eliminate the copy/move in this case. As such, the type must still have a copy/move constructor to use to initialize the return value. And since lock_guard does not, this is still a compile error. Implementations are allowed to refuse to elide copies when passing or returning an object of trivially-copyable type. This is to allow moving such objects around in registers, which some ABIs might mandate in their calling conventions.

struct trivially_copyable {
    int a;  
};

void foo (trivially_copyable a) {}

foo(trivially_copyable{}); //copy elision not mandated

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you:


Copy elision:
* Guaranteed copy elision

Table Of Contents
8 Arrays
11 Loops
39 Streams
51 Unions
56 Lambdas
60 SFINAE
62 RAII
67 Sorting
75 Copy elision
84 RTTI
87 Scopes
104 Profiling
107 Recursion
117 Iteration
125 Alignment
134 Semaphore
136 Debugging
139 Mutexes
142 decltype