Consider the following code snippet:
#include <iostream>
#include <initializer_list>
struct C
{
C(std::initializer_list<int>) { std::cout << "list\n"; }
C(std::initializer_list<int>, std::initializer_list<int>) { std::cout << "twice-list\n"; }
};
int main()
{
C c1 { {1,2}, {3} }; // twice-list ctor
C c2 { {1}, {2} }; // why not twice-list ?
return 0;
}
Live demo.
Why scalar values in braces for c2
variable are not interpreted as separate std::initializer_list?
First, something very important: You have two different kinds of constructors. The first in particular, C(std::initializer_list<int>)
, is called an initializer-list constructor. The second is just a normal user-defined constructor.
[dcl.init.list]/p2
A constructor is an initializer-list constructor if its first parameter is of type
std::initializer_list<E>
or reference to possibly cv-qualifiedstd::initializer_list<E>
for some typeE
, and either there are no other parameters or else all other parameters have default arguments (8.3.6).
In a list-initialization containing one or more initializer-clauses, initializer-list constructors are considered before any other constructors. That is, initializer-list constructors are initially the only candidates during overload resolution.
[over.match.list]/p1
When objects of non-aggregate class type
T
are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, 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 for both declarations of c1
and c2
the candidate set consists only of the C(std::initializer_list<int>)
constructor.
After the constructor is selected the arguments are evaluated to see if there exists an implicit conversion sequence to convert them to the parameter types. This takes us to the rules for initializer-list conversions:
[over.ics.list]/p4 (emphasis mine):
Otherwise, if the parameter type is
std::initializer_list<X>
and all the elements of the initializer list can be implicitly converted toX
, the implicit conversion sequence is the worst conversion necessary to convert an element of the list toX
, or if the initializer list has no elements, the identity conversion.
This means a conversion exists if each element of the initializer list can be converted to int
.
Let's focus on c1
for now: For the initializer-list {{1, 2}, {3}}
, the initializer-clause {3}
can be converted to int
([over.ics.list]/p9.1), but not {1, 2}
(i.e int i = {1,2}
is ill-formed). This means the condition for the above quote is violated. Since there is no conversion, overload resolution fails since there are no other viable constructors and we are taken back to the second phase of [over.match.list]/p1:
- 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.
Notice the change in wording at the end. The argument list in the second phase is no longer a single initializer-list, but the arguments of the braced-init-list used in the declaration. This means we can evaluate the implicit conversions in terms of the initializer-lists individually instead of at the same time.
In the initializer-list {1, 2}
, both initializer-clauses can be converted to int
, so the entire initializer-clause can be converted to initializer_list<int>
, the same for {3}
. Overload resolution is then resolved with the second constructor being chosen.
Now let's focus on c2
, which should be easy now. The initializer-list constructor is first evaluated, and, using { {1}, {2} }
there surely exists a conversion to int
from {1}
and {2}
, so the first constructor is chosen.
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