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?
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 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.
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.
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 ofF
.
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.
{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.
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