Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does guaranteed copy elision work with function parameters?

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?
like image 906
Maël Nison Avatar asked May 29 '17 16:05

Maël Nison


People also ask

What is guaranteed copy elision in C++?

Namely, there had to be an accessible copy and/or move constructor. Guaranteed copy elision redefines a number of C++ concepts, such that certain circumstances where copies/moves could be elided don't actually provoke a copy/move at all. The compiler isn't eliding a copy; the standard says that no such copying could ever happen.

When to use copy elision in return statements?

In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".

What happens if the compiler cannot perform copy elision?

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.

What is copy elision in C++11?

The standard allows this copy to be elided by constructing the return value at the call-site rather than in make ( C++11 [class.copy]/31 ). This is called copy elision. The unfortunate part is this: even if all copies of the type are elided, the constructor still must exist. This means that if we instead have:


2 Answers

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.

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.

like image 108
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 00:10

Yakk - Adam Nevraumont


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.

like image 39
Rakete1111 Avatar answered Oct 12 '22 23:10

Rakete1111