As pointed out by Nicol Bolas, the original version of this answer was incorrect: cppreference at the time of writing incorrectly documented the order in which constructors were considered in list-initialization. Below is an answer using the rules as they exist in the n4140 draft of the standard, which is very close to the official C++14 standard.
The text of the original answer is still included, for the record.
Updated Answer
Per NathanOliver's comment, gcc and clang produce different outputs in this situation:
g++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
initializer list
clang++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
gcc is correct.
n4140 [dcl.init.list]/1
List-initialization is initialization of an object or reference from a braced-init-list.
You're using list-initialization there, and since c
is an object, the rules for its list-initialization are defined in [dcl.init.list]/3:
[dcl.init.list]/3:
List-initialization of an object or reference of type T is defined as follows:
- If
T
is an aggregate...
- Otherwise, if the initializer list has no elements...
- Otherwise, if
T
is a specialization of std::initializer_list<E>
...
going through the list so far:
Foo
is not an aggregate.
- It has one element.
Foo
is not a specialization of std::initializer_list<E>
.
Then we hit [dcl.init.list]/3.4:
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.
Now we're getting somewhere. 13.3.1.7 is also known as [over.match.list]:
Initialization by list-initialization
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.
So the copy constructor will only be considered after the initializer list constructors, in the second phase of overload resolution. The initializer list constructor should be used here.
It's worth noting that [over.match.list] then continues with:
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.
and that after [dcl.init.list]/3.5 deals with single-element list initialization:
Otherwise, if the initializer list has a single element of type E
and either T
is not a reference type or its referenced type is reference-related to E
, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T
, the program is ill-formed.
which explains where cppreference got their special case for single-element list initialization, though they placed it higher in the order than it should be.
Original Answer
You're encountering an interesting aspect of list initialization, where if the list fulfills certain requirements it may be treated like a copy-initialization rather than a list-initialization.
from cppreference:
The effects of list initialization of an object of type T
are:
If T
is a class type and the initializer list has a single element of
the same or derived type (possibly cv-qualified), the object is
initialized from that element (by copy-initialization for
copy-list-initialization, or by direct-initialization for
direct-list-initialization). (since c++14)
Foo c{b}
fulfills all these requirements.