4

In the following example I am expecting only a single copy-construction, as I thought the intermediate copies would by copy elided. The only required (I thought?) copy would be in the constructor of B to initialize the member variable a.

#include <iostream>

struct A
{
    A() = default;
    A(A const&) { std::cout << "copying \n"; }
};

struct B
{
    B(A _a) : a(_a) {}
    A a;    
};

struct C : B
{
    C(A _a) : B(_a) {}
};

int main()
{
    A a{};
    C c(a);
}

When I execute this code (with -O3) I see the following output

copying 
copying 
copying 

Why aren't these intermediate copies elided?

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • elision cannot be applied in that case, and as-if rule could not apply neither as you have output. – Jarod42 Mar 24 '20 at 13:20

3 Answers3

6

Here are the cases, where copy elision is allowed (class.copy/31):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

  • in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object

  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted

None of these are true for your example (we are not in a return statement, throw-expression or exception-declaration. And there are no temporaries in your example at all.), so copy happens each time you expect to happen.

Note, that copy elision is allowed in the mentioned cases, but not mandatory. So even, for these cases, the compiler is allowed to emit copies (this is true for C++11. In C++17, there are some cases, where copy elision is mandatory. But, none of your example cases allows elision in C++17 either.)

geza
  • 28,403
  • 6
  • 61
  • 135
1

Expanding on the discussion under the other answer, the copy here has side effects (printing to console), and so does not qualify for copy optimization.

Copy elision, however, is allowed regardless of side effects, per the cppreference article on copy elision. It's not allowed here because you have lvalues all the way. None of the copies you hoped to eliminate were of an rvalue or prvalue. To make them an rvalue, you need to cast using std::move or construct them as nameless temporaries as part of the constructor call.

Again, the cppreference article explains it much better.

Zuodian Hu
  • 979
  • 4
  • 9
  • The point isn't really whether it is rvalue or lvalue. Even if OP uses `std::move` the move constructions are not allowed to be elided. There are only very specific cases in which elision is allowed, listed in @geza's answer and the one relating to `return` statements even applies to lvalues. Nameless temporaries would only help to (maybe) elide *one* of the copies/moves. – walnut Mar 24 '20 at 17:32
  • Yep, you're right. I didn't realize until I read the list again. more carefully. – Zuodian Hu Mar 24 '20 at 18:37
-2

You are passing the a object to the C constructor by value. And then it is being passed to the B constructor by value.

Anon Mail
  • 4,660
  • 1
  • 18
  • 21
  • 1
    Passing the argument by-value was deliberate for this example. It was my understanding that [arguments passed by value could/would also be elided](https://stackoverflow.com/questions/33872026/copy-elision-for-pass-by-value-arguments) – Cory Kramer Mar 24 '20 at 13:11
  • @CoryKramer apparently not. – Anon Mail Mar 24 '20 at 13:13
  • I can see that :) I was hoping for an explanation of why. Is there some special requirement of the class for this to occur? Compiler setting? Mention in the standard of when this can/should occur? – Cory Kramer Mar 24 '20 at 13:14
  • @CoryKramer try adding any data member in `A`. Maybe compiler doesn't care much about structs with 0 data – Alexey S. Larionov Mar 24 '20 at 13:18