#include <iostream>
#include <string>
#include <initializer_list>
class A
{
public:
A(int, bool) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A(int, double) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A(std::initializer_list<int>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
int main()
{
A a1 = {1, 1.0};
return 0;
}
(This question is a follow-up to this.)
The above program fails to compile with clang35 -std=c++11
init.cpp:15:14: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
A a1 = {1, 1.0};
^~~
init.cpp:15:14: note: insert an explicit cast to silence this issue
A a1 = {1, 1.0};
^~~
static_cast<int>( )
while g++48 -std=c++11
chooses a produce a warning to diagnose the ill-formed narrowing
init.cpp: In function ‘int main()’:
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
A a1 = {1, 1.0};
^
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
and produces the result
A::A(std::initializer_list<int>)
My question is if A::A(std::initializer_list<int>)
should be a viable overload. Below are standard quotes that I think imply that the initializer_list
overload should not be viable.
From 13.3.2 [over.match.viable]
Second, for
F
to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter ofF
.
From 4 [conv]
An expression
e
can be implicitly converted to a typeT
if and only if the declarationT t=e
; is well-formed, for some invented temporary variablet
.
From 8.5.1 [dcl.init.aggr]
If the initializer-clause is an expression and a narrowing conversion is required to convert the expression, the program is ill-formed.
Using 8.5.1
and 4
, since the following is not well-formed
std::initializer_list<int> e = {1, 1.0};
{1, 1.0}
is not implicitly convertible to std::initializer_list<int>
.
Using the quote from 13.3.2
, shouldn't it imply that A::A(std::initializer_list<int>)
isn't a viable function when doing overload resolution for A a1 = {1, 1.0};
? Finding no viable initializer_list
constructors, shouldn't this statement pick A::A(int, double)
?
I believe that the problem in your analysis is the fact that the statement
int t = 1.0;
is indeed well-formed - an implicit conversion from double
to int
obviously exists. [over.ics.list]/4 also describes it:
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.
Every element from the initializer list can be implicitly converted to int
, thus the constructor is viable and chosen. However, only once it is chosen, the whole thing hard-errors, [dcl.init.list]/(3.6):
The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
As you can see, the constructor to call is determined before the narrowing-check is performed. In other words, the viability of an initializer-list constructor is not depending on narrowing of any arguments.
Thus the code should be ill-formed.
One way to get the desired behavior is to use a constructor template with SFINAE
template <typename T, typename=std::enable_if_t<std::is_same<int, T>{}>>
A(std::initializer_list<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
Demo.