为什么这个 initializer_list 构造函数是可行的重载?

Why is this initializer_list constructor a viable overload?

#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;
}

(此问题是 的后续问题。)

以上程序无法通过 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>( )

g++48 -std=c++11 选择生成警告以诊断格式错误的缩小

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]

并产生结果

A::A(std::initializer_list<int>)

我的问题是 A::A(std::initializer_list<int>) 是否应该是一个可行的重载。以下是我认为暗示 initializer_list 重载不可行的标准引述。

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

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

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

使用 8.5.14,因为以下格式不正确

std::initializer_list<int> e = {1, 1.0};

{1, 1.0} 不能 隐式转换 std::initializer_list<int>.

引用 13.3.2 中的引述,难道不应该暗示 A::A(std::initializer_list<int>) 在为 A a1 = {1, 1.0}; 进行重载解析时不是一个可行的函数吗?找不到可行的 initializer_list 构造函数,这个语句不应该选择 A::A(int, double) 吗?

我认为您分析中的问题在于陈述

int t = 1.0;

确实格式正确 - 从 doubleint 的隐式转换显然存在。 [over.ics.list]/4 也描述了它:

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.

初始化列表中的每个元素都可以隐式转换为int,因此构造函数是可行的并被选中。然而,只有一旦它被选中,整个事情就会出现硬错误,[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.

如您所见,要调用的构造函数是在执行缩小检查之前确定的。换句话说,初始化列表构造函数的可行性不取决于任何参数的缩小。
因此代码应该是错误的。

获得所需行为的一种方法是使用带有 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.