Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why scalar in braces are not interpeted as initializer_list

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?

like image 443
αλεχολυτ Avatar asked Jul 03 '15 16:07

αλεχολυτ


1 Answers

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-qualified std::initializer_list<E> for some type E, 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 to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, 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.

like image 100
David G Avatar answered Sep 28 '22 06:09

David G