10

Before C++11, we can do copy initialization by writing something like A a = 1; which is more or less equivalent to A a = A(1);. That is, a temporary is first created and then a copy ctor is invoked. Regardless of copy elision, this must be so conceptually and the copy ctor must be accessible.

With list initialization in C++11, we can do a copy list initialization by writing A a = {1, 2};. In my opinion, this should be more or less equivalent to A a = A(1, 2);. However, on GCC and clang, A a = {1, 2} compiles even when the copy and move ctor are inaccessible (by declaring as private). Still, A a = 1; does not compile on GCC or clang if the corresponding copy/move ctor is inaccessible. So, A a = {1, 2}; seems more or less equivalent to A a{1, 2}; which is direct list initialization. The difference between this and the real direct list initialization is that A a = {1, 2}; does not compile if the ctor that takes two ints are explicit. In this aspect, A a = {1, 2}; resembles copy initialization.

So, my question is: what is the exact semantics of expressions like A a = {1, 2}; conceptually? By conceptually, copy elision do not stay in the way.

Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 3
    You've explained everything yourself in the question. Copy list initialization is the same as direct list initialization except the former does not consider explicit constructors. Neither requires an accessible copy constructor. – Praetorian Nov 17 '14 at 01:40
  • @Praetorian This does not seem exactly like this. See my comment to Columbo's answer. – Lingxi Nov 17 '14 at 02:19

1 Answers1

11

The standard describes it pretty well; [dcl.init.list]/3:

List-initialization of an object or reference of type T is defined as follows:

  • [...]
  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.

[over.match.list] (emphasis mine):

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.

  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted.
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

Hence, if no initializer-list constructor is found (as in your case), the elements of the initializer list constitute the arguments for the constructor call.
In fact, the only difference of direct-list-initialization and copy-list-initialization is covered by the last, bolded sentence.

This is one of the advantages of list-initialization: It doesn't necessitate the presence of a special member function that is not gonna be used anyway.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Then, how to explain copy-list-initialization in the context of pass-by-value function call, e.g., `f({1, 2});`. On GCC and clang, this requires the copy/move ctor to be accessible. Otherwise, this program does not compile. – Lingxi Nov 17 '14 at 02:10
  • I tested it on the embedded online compiler of cppreference.com. E.g, you may find it at the bottom of http://en.cppreference.com/w/cpp/language/explicit – Lingxi Nov 17 '14 at 02:32