【问题标题】:Why is this initializer_list constructor a viable overload?为什么这个 initializer_list 构造函数是一个可行的重载?
【发布时间】:2015-03-20 09:02:50
【问题描述】:
#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的后续问题。)

上面的程序使用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&lt;int&gt;) 是否应该是一个可行的重载。以下是我认为暗示initializer_list 重载不可行的标准引号。

来自13.3.2 [over.match.viable]

其次,要使F 成为一个可行的功能,每个功能都应存在 argument 一个转换该参数的隐式转换序列 到F的对应参数。

来自4 [conv]

表达式 e 可以隐式转换为类型 T 如果并且 仅当声明T t=e;是良构的,对于一些发明的 临时变量t

来自8.5.1 [dcl.init.aggr]

如果 initializer-clause 是一个表达式和一个窄化 转换需要转换表达式,程序是 格式不正确。

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

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

{1, 1.0} 不能隐式转换std::initializer_list&lt;int&gt;

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

【问题讨论】:

  • 8.5.1如何申请int t = 1.0;?那不是聚合初始化,是吗?
  • 在帖子中指出,您链接到采用初始化列表的构造函数是非常受欢迎的。所以编译器选择初始化列表构造函数,然后尝试转换,因为它缩小了编译失败。
  • @Columbo 对不起,我没明白你的意思。另外,对于8.5.1,我没有粘贴整个标准引用,但是由于您提到聚合初始化,我想这已经足够清楚了。
  • @NathanOliver 是的,但“强烈偏好”是 Scott Meyers 将标准放在简单的英语中。我在问它是如何遵循标准的。
  • @Pradhan 我发布了一个答案以使我的观点更清楚。

标签: c++ c++11 language-lawyer overload-resolution list-initialization


【解决方案1】:

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

int t = 1.0;

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

否则,如果参数类型为std::initializer_list&lt;X&gt;且所有 初始化列表的元素可以隐式转换为 X,隐式转换序列是最差的转换 必须将列表中的元素转换为 X,或者如果 初始化器列表没有元素,身份转换。

初始化列表中的每个元素都可以隐式转换为int,因此构造函数是可行的并且可以选择。然而,只有一旦它被选中,整个事情就会出现硬错误,[dcl.init.list]/(3.6):

枚举适用的构造函数并选择最佳的构造函数 通过重载决议(13.3、13.3.1.7)。 如果缩小 转换(见下文)需要转换任何参数, 程序格式不正确。

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

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

【讨论】:

  • 啊好吧,这似乎解释了它。仔细检查这些部分以确保我理解。您认为标准是以这种看似复杂的方式指定它的吗?为什么不简单地确保它不是一个可行的超载呢?是为了避免使聚合初始化复杂化吗? IOW 他们必须区分&lt;some pod&gt; = {1, 1.0}&lt;some init_list&gt; = {1, 1.0}
  • @Pradhan 我不知道。看来要么我们遗漏了一些极端案例,要么该部分有缺陷。
  • 我认为如果使用的构造函数依赖于参数的值会很奇怪(例如,从整数到整数类型的转换,其中源是常量表达式)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-25
  • 1970-01-01
  • 2012-02-10
  • 1970-01-01
  • 2013-07-28
  • 1970-01-01
  • 2015-10-19
相关资源
最近更新 更多