Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous overload resolution with initializer_list

I tested the following code on Visual Studio and it compiles and prints "A(double)".

#include <iostream>
#include <initializer_list>

struct A {
    A(std::initializer_list<int>) { puts("initializer_list<int>"); }        // (1)
    A(std::initializer_list<float>) { puts("initializer_list<float>"); }    // (2)
    A(double) { puts("A(double)"); }                                        // (3)
};

int main() {
    A var{ 1.1 };   
}

However both IntelliSense and http://ideone.com/ZS1Mcm disagree, saying that more than one instance of constructor "A::A" matches the argument list (meaning both initializer-list constructors). Note that if either (1) or (2) is removed, code does not compile anymore, as "conversion from 'double' to 'float' requires a narrowing conversion".

Is this a bug? The behaviour feels inconsistent, but I see the same behaviour in VS13 and VS15 so maybe there is more to it?

like image 922
Karlis Olte Avatar asked Jul 30 '15 15:07

Karlis Olte


1 Answers

The code is ill-formed. §8.5.4/(3.6) applies:

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).

Now, §13.3.3.1.5 goes

When an argument is an initializer list (8.5.4), it is not an expression and special rules apply for converting it to a parameter type. […] 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.

Converting 1.1, which is of type double (!), to int is a Floating-integral conversion with Conversion rank, while the conversion from 1.1 to float is a Floating point conversion - also having Conversion rank.

enter image description here

Thus both conversions are equally good, and since §13.3.3.2/(3.1) cannot distinguish them either, the call is ambiguous. Note that narrowing doesn't play a role until after overload resolution is done and hence cannot affect the candidate set or the selection process. More precisely, a candidate must meet the requirement set in 13.3.2/3:

Second, for F to be a viable function, there shall exist for each argument an implicit conversion sequence (13.3.3.1) that converts that argument to the corresponding parameter of F.

However, as shown in the second quote, the implicit conversion sequence that converts {1.1} to std::initializer_list<int> is the worst conversion from 1.1 to int, which is a Floating-integral conversion - and a valid (and existing!) one at that.


If instead you pass {1.1f} or alter the initializer_list<float> to <double>, the code is well-formed, as converting 1.1f to float is an identity conversion. The standard gives a corresponding example in (3.6):

[ Example:

struct S {
    S(std::initializer_list<double>); // #1
    S(std::initializer_list<int>);    // #2

};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1

end example ]

Even more interestingly,

struct S {
    S(std::initializer_list<double>); // #1
    S(std::initializer_list<int>);    // #2

};
S s1 = { 1.f }; // invoke #1 

Is also valid - because the conversion from 1.f to double is a Floating point promotion, having Promotion rank, which is better than Conversion rank.

like image 150
Columbo Avatar answered Sep 30 '22 18:09

Columbo