3

In the following program the object A a is directly initialized from braced-init-list {A{}}:

#include <iostream>

struct A {
    int v = 0;
    A() {}
    A(const A &) : v(1) {}
};

int main() {
    A a({A{}});
    std::cout << a.v;
}

MSVC and GCC print 0 here meaning that copy-elision takes place. And Clang prints 1 executing the copy-constructor. Online demo: https://gcc.godbolt.org/z/1vqvf148z

Which compiler is right here?

Jason
  • 36,170
  • 5
  • 26
  • 60
Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 2
    What makes you think any of them are "right"? – Nicol Bolas Aug 13 '22 at 14:24
  • 1
    Basically, you reach https://timsong-cpp.github.io/cppwp/n4861/dcl.init#17.6.2. Prolly should be another case for https://cplusplus.github.io/CWG/issues/2311.html – Language Lawyer Aug 13 '22 at 14:36
  • 1
    AFAIK Both are, your copy constructor has side effects (which may be elided away). It is up to the programmer to explicitly avoid copy elision if the side effect is necessary. I'd like to be corrected if I'm wrong :) – Pepijn Kramer Aug 13 '22 at 14:36
  • @PepijnKramer, if one deletes copy constructor then Clang/MSVC will reject the program, and GCC still accepts it, online demo: https://gcc.godbolt.org/z/EWq4qW79a Are all compilers still right? :) – Fedor Aug 14 '22 at 20:12

1 Answers1

1

Which compiler is right here?

I think that clang is right in using the copy constructor and printing 1 for the reason(s) explained below.

First note that A a({A{}}); is direct-initialization as can be seen from dcl.init#16.1:

  1. The initialization that occurs:

16.1) for an initializer that is a parenthesized expression-list or a braced-init-list,

16.2) for a new-initializer,

16.3) in a static_­cast expression ([expr.static.cast]),

Now, dcl.init#17.6 is applicable here:

17.6) Otherwise, if the destination type is a (possibly cv-qualified) class type:

17.6.1) 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. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example ]

17.6.2) Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). Then:

17.6.2.1) If overload resolution is successful, the selected constructor is called to initialize the object, with the initializer expression or expression-list as its argument(s).

(emphasis mine)

This means that the copy constructor(which is the selected constructor here) will be used/called to initialize the object named a with the expression-list as its argument and since in your copy ctor's member initializer list you're initializing a.v to 1, the output printing 1 of clang is correct.

Jason
  • 36,170
  • 5
  • 26
  • 60