Based on this code
struct Foo
{
Foo()
{
cout << "default ctor" << endl;
}
Foo(std::initializer_list<Foo> ilist)
{
cout << "initializer list" << endl;
}
Foo(const Foo& copy)
{
cout << "copy ctor" << endl;
}
};
int main()
{
Foo a;
Foo b(a);
// This calls the copy constructor again!
//Shouldn't this call the initializer_list constructor?
Foo c{b};
_getch();
return 0;
}
The output is:
default ctor
copy ctor
copy ctor
In the third case, I'm putting b into the brace-initialization which should call the initializer_list<> constructor.
Instead, the copy constructor takes the lead.
Will someone of you tell me how this works and why?
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.
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 ofstd::initializer_list<E>
...
going through the list so far:
Foo
is not an aggregate.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 typeT
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 eitherT
is not a reference type or its referenced type is reference-related toE
, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element toT
, 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.
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.
Let us examine what the C++14 specification says about list initialization here. [dcl.init.list]3 has a sequence of rules which are to be applied in order:
3.1 does not apply, since Foo
is not an aggregate.
3.2 does not apply, since the list is not empty.
3.3 does not apply, since Foo
is not a specialization of initializer_list
.
3.4 does apply, since Foo
is a class type. It says to consider constructors with overload resolution, in accord with [over.match.list]. And that rule says to check initializer_list
constructors first. Since your type has an initilaizer_list
constructor, the compiler must check to see if an initializer_list
matching one of those constructors can be manufactured from the given values. It can, so that is what must be called.
In short, GCC is right and Clang is wrong.
It should be noted that the C++17 working draft changes nothing about this. It has a new section 3.1 that has special wording for single-value lists, but that only applies to aggregates. Foo
is not an aggregate, so it does not apply.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With