(All ISO Standard references below refer to N4659: March 2017 post-Kona working draft/C++17 DIS, and all example program results are consistent over GCC and Clang for C++11, C++14 and C++17)
Consider the following example:
#include <initializer_list>
// Denote as A.
void f(float) {}
// Denote as B.
void f(std::initializer_list<int>) {}
int main() {
// Denote call as C1.
f(1.5F); // Overload resolution picks A (trivial).
// Denote call as C2.
f({1.5F}); // Overload resolution picks B (and subsequently fails; narrowing).
return 0;
}
which results in the call C2 picking overload B as best viable function, followed by a failure due to narrowing in list initialization (governed by [dcl.init.list]/7):
error: type 'float' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
Question
- Why does the
f({1.5F});function call findvoid f(std::initializer_list<int>)to be the unique best viable function, ranked as a better match thanvoid f(float)? Afaics this conflicts with [over.ics.list]/4 and [over.ics.list]/9 (see details below).
I'm looking for references to the relevant standard passages.
Note that I am aware of the special rules regarding overload resolution for list initialization (constructor overloading) and std::initializer_list<> (and the various SO questions on this topic), where std::initializer_list<> is strongly preferred, as governed by [over.match.list]/1. However, afaics, this does not apply here (or, if I'm wrong, at the very least arguably conflicts with the standard examples shown in [over.ics.list]/4).
Details and investigation of my own
For both C1 and C2 calls above, overload resolution is applied as specified in [over.call.func] (as per [over.match]/2.1 and [over.match.call]/1), particularly [over.call.func]/3, and the set of candidate functions for both calls is:
candidate_functions(C1) = { void f(float),
void f(std::initializer_list<int>) }
candidate_functions(C2) = { void f(float),
void f(std::initializer_list<int>) }
and the argument list in each call is the same as the expression-list in the call.
As per [over.match.viable]/2 and [over.match.viable]/3, the set of viable functions is the same as the candidate functions for both calls:
viable_functions(C1) = { void f(float),
void f(std::initializer_list<int>) }
viable_functions(C2) = { void f(float),
void f(std::initializer_list<int>) }
Best viable functions
As per [over.match.best]/1, in short terms, the best viable function for a given function call, here specifically a single-argument call (with no special cases for the viable functions of each call), is the viable function that, for the single argument, has the best implicit conversion sequence from the single argument to the single parameter of the respective viable function.
C1: Best viable function
The call f(1.5F) has an identity standard conversion (no conversion) and thus an exact match for A (as per [over.ics.scs]/3) and A can be (trivially and) non-ambiguously chosen as the best viable function.
best_viable_function(C1) = void f(float)
C2: Best viable function
Afaics, ranking the implicit conversion sequences of the call f({1.5F}) towards viable candidates is governed by [over.ics.list], as per [over.ics.list]/1:
When an argument is an initializer list ([dcl.init.list]), it is not an expression and special rules apply for converting it to a parameter type.
Matching C2 towards A
For matching towards A where the single parameter is of the fundamental float type, [over.ics.list]/9 and particularly [over.ics.list]/9.1 applies [emphasis mine]:
Otherwise, if the parameter type is not a class:
(9.1) if the initializer list has one element that is not itself an initializer list, the implicit conversion sequence is the one required to convert the element to the parameter type; [ Example:
void f(int); f( {'a'} ); // OK: same conversion as char to int f( {1.0} ); // error: narrowing— end example ]
[...]
This arguably means that the implicit conversion sequence for matching the call f({1.5F}} to f(float) is the same conversion sequence as float to float; i.e., identity conversion and subsequently an exact match. However, as per the example above, there must be some flaw in my logic here as the call C2 doesn't even result in an ambiguous best viable function.
Matching C2 towards B
For matching towards B where the single parameter [over.ics.list]/4 applies [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 conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [ Example:void f(std::initializer_list<int>); f( {} ); // OK: f(initializer_list<int>) identity conversion f( {1,2,3} ); // OK: f(initializer_list<int>) identity conversion f( {'a','b'} ); // OK: f(initializer_list<int>) integral promotion f( {1.0} ); // error: narrowing [...]— end example ]
In this example, the implicit conversion sequence is thus the worst conversion necessary to convert the single float element of the list to int, which is a conversion ranked standard conversion sequence ([over.ics.scs]/3), particularly a narrowing conversion as per [conv.fpint]/1.
Best viable function
According to my own interpretation of the standard passages as per above, the best viable function should be the same as for the C1 call,
best_viable_function(C2) = void f(float) ?
but I'm obviously missing something.