Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy Elision for Returned Temporaries

I'm trying to understand lifetime guaranteed by the C++17 Standard, in particular for guaranteed copy elisions. Let's start with an example

std::string make_tmp();

std::string foo() {
   return std::string{make_tmp().c_str()};
}

My undestanding of what's happening: make_tmp creates a temporary string we will call t; foo returns a (needlessly created) temporary (copy of t's c_str). The standard (even pre C++17) guarantees the lifetime of t to be the time until the full return expressions has been evaluated. Thus it is safe so create the temporary copy of t (to be returned).

Now copy elisions kicks in; more specifically, the second bullet in the first C++17 block:

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.

Consequently the temporary copy will not even be created at all.

Follow-up Questions:

  1. Does the returned temporary copy still imply a sufficiently extended lifetime of t -- even though it is guaranteed to be elided?

  2. Consider the variant of foo given below. I'm assuming, copy elision is no longer required (but rather highly likely). If the copy will not be elided, the standard's got us covered (by the arguments above). In case the copy is elided, does the standard still guarantee a sufficient lifetime of t despite the type of the returned-expression being different from foo's return type?

foo-Variant:

std::string foo() {
   return make_tmp().c_str();
}

I'd like to understand the guarantees purely implied by the standard. Please note, that I'm aware of the fact that both foo versions "work" (ie. there are no dangling pointers involved even when testing with custom classes under various compilers).

like image 269
m8mble Avatar asked May 25 '18 12:05

m8mble


People also ask

Is copy elision guaranteed?

Copy elision (NRVO) is allowed there and is routinely performed by most compilers, but is still non-guaranteed, and the widget class cannot be non-copyable non-movable.

What is copy elision in Javascript?

Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).

What is NRVO?

This particular technique was later coined "Named return value optimization" (NRVO), referring to the fact that the copying of a named object is elided.


2 Answers

I think there's some confusion here as to which copies are being elided. Let's take the furthest out view:

std::string make_tmp();

std::string foo() {
   return std::string{make_tmp().c_str()};
}

std::string s = foo();

Here, potentially there are four std::strings created: make_tmp(), the temporary std::string{...} constructed from it, the return object of foo(), and s. Which implies three copies (I'm just going to use the word copy for consistency, even if all of these are moves. Hopefully this won't be confusing).

Copy elision allows for the removal of two of these copies:

  • The copy from std::string{...} to the return object of foo().
  • The copy from foo() to s

Both of these elisions are mandated in C++17's "guaranteed copy elision" - because we're initializing from a prvalue (a term that's a little confusing in that we're not actually performing overload resolution to determine we need to perform a copy construction and then skipping it, we're just directly initializing). The code is identical to:

std::string s{make_tmp().c_str()};

This cannot be removed though - we're still constructing a string via make_tmp(), pulling out its contents, and then constructing a new string from them. No way around that.

The provided variant has exactly the same behavior.

like image 63
Barry Avatar answered Oct 21 '22 21:10

Barry


This answer directly answers the lifetime issues asked in OP (and you can see it has nothing to do with copy elision). If you are not familiar with the whole story happened during the execution of the return statement, you can refer to Barry's answer.


Yes, the temporary is guaranteed to persist during the copy-initialization of the returned object per [stmt.return]/2:

The copy-initialization of the result of the call is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables ([stmt.jump]) of the block enclosing the return statement.

like image 20
xskxzr Avatar answered Oct 21 '22 21:10

xskxzr