Firstly, something very important: you have two different types of constructors. The first, in particular, C(std::initializer_list<int>) , is called the list constructor-initializer. The second is a regular custom constructor.
[dcl.init.list] / p2
A constructor is a constructor of an initializer list if its first parameter is of type std::initializer_list<E> or a link to a possible cv-qualified std::initializer_list<E> for some type E , and either there are no other parameters, or all other parameters have default arguments (8.3.6).
In initializing a list containing one or more initializer clauses, initializer constructors are considered before any other constructors. That is, initializer list constructors are initially the only candidates for overload resolution.
[over.match.list] / p1
When objects of type non-aggregate class T initialized from the list, so 8.5.4 indicates that overload resolution is performed in accordance with the rules in this section, the overload resolution is selected by the constructor in two phases:
Initially, candidate functions are initializer-list constructors (8.5.4) of class T , and the argument list consists of a list of initializers as one argument.
If no viable initializer list constructor is found, overload resolution is performed again, where the candidate functions are all class T constructors, and the argument list consists of elements from the initializer list.
So, for both declarations c1 and c2 set of candidates consists only of the constructor C(std::initializer_list<int>) .
After choosing a constructor, the arguments are evaluated to see if an implicit sequence of transformations exists to convert them to parameter types. This will lead us to the conversion rules with an initialization list:
[over.ics.list] / p4 (emphasis added):
Otherwise, if the parameter type is std::initializer_list<X> , and all elements of the initializer list can be implicitly converted to X , the implicit conversion sequence is the worst conversion needed to convert the list element to X , or if there are no elements in the initializer list, identity conversion.
This means that a conversion exists if each element of the list of initializers can be converted to int .
Now focus on c1 : for the initializer list {{1, 2}, {3}} , the initializer sentence {3} can be converted to int ([over.ics.list] /p9.1), but not {1, 2} (that is, int i = {1,2} poorly formed). This means that the condition for the above quote is violated. Since there is no conversion, overload resolution fails because there are no other viable constructors, and we return to the second phase [over.match.list] / p1:
- If no viable initializer list constructor is found, overload resolution is performed again, where the candidate functions are all constructors of class
T , and the argument list consists of elements from the initializer list.
Note the wording change at the end. The argument list in the second step is no longer the only list of initializers, but the arguments to the braced-init-list used in the declaration. This means that we can evaluate implicit conversions in terms of initializer lists individually, and not at the same time.
In the list of initializers {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 enabled when a second constructor is selected.
Now let's focus on c2 , which should now be easy. First, the constructor of the initializer list is computed, and using { {1}, {2} } , perhaps there is a conversion to int from {1} and {2} , so the first constructor is selected.