Does guaranteed copy elision work with function parameters?
Asked Answered
P

2

8

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?
Pekan answered 29/5, 2017 at 16:7 Comment(5)
Note: You're using C++17, so why are you still putting void if the function takes no parameters?Exploiter
@Exploiter was there any standard that required to put that void? must have been long before my time with c++Cyprinoid
@tobi303 I don't know. Probably before C++ was standardized though, when it was still based on C. But I don't know.Exploiter
Hm I always put void - in C I remember there was a difference between empty and void (iirc empty allows any number of parameters?), and I guess I grown accustomed to it :)Liverwurst
foo(void) is only needed in C because in C foo() means a function taking in any parameters. In C++ foo() means a function with no parameters, foo(void) is only supported for backward compatibility.Embattled
D
8

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.

(The instructions, by the way, include time travel provisions; the time-point of construction remains inside the function itself!)

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.

Demonolater answered 30/5, 2017 at 17:52 Comment(0)
E
2

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.

Exploiter answered 29/5, 2017 at 16:22 Comment(5)
Copy? I think you mean move. Eliding construction in the C++ standard refers to something different than the as-if rule; did you mean to use eliding?Demonolater
And C++17 makes certain kinds of things not-objects; no omitted move/copy occurs.Demonolater
@Yakk Yes, you're right! :) But I don't understand your point about not-objects. Can you please elaborate?Exploiter
In C++14, prvalues are temporary objects. In C++17, prvalues are something else; basically, instructions on how to construct an object. A temporary object can be materialized out of a prvalue in certain contexts, but they can also be used to directly construct an object. See this SO answer.Demonolater
"But, the compiler might actually elide the only move altogether, if the move constructor has no side effects." Er, isn't a fundamental point of copy/move elision that they're the only context in which it doesn't matter if there are side effects, as elision is permitted to discard said side effects?Embraceor

© 2022 - 2024 — McMap. All rights reserved.