Why is this initializer_list constructor a viable overload?

一曲冷凌霜 提交于 2019-12-24 05:12:31

问题


#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 of F.

From 4 [conv]

An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t.

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


回答1:


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

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.



来源:https://stackoverflow.com/questions/28074071/why-is-this-initializer-list-constructor-a-viable-overload

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!