问题 listizer初始化和initializer_list构造函数的重载失败失败


以下无法编译 clang35 -std=c++11

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

有错误

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

OTOH,它警告缩小和编译 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>)

这两种行为都有意义吗?引自 cppreference

将std :: initializer_list作为唯一参数的所有构造函数,   或者作为第一个参数,如果其余参数有默认值   通过对a的重载决策来检查和匹配值   std :: initializer_list类型的单个参数

如果前一个阶段没有产生匹配,则T的所有构造函数   参与对这组参数的重载决策   由带有限制的braced-init-list元素组成   只允许非缩小转换。如果这个阶段   生成一个显式构造函数作为a的最佳匹配   copy-list-initialization,编译失败(注意,简单   复制初始化,根本不考虑显式构造函数)

由于不允许缩小转换,我希望重载决策步骤不匹配 A(std::initializer_list<int>) 构造函数,而不是匹配 A(int, double) 一。例如,改变 A(std::initializer_list<int>) 至 A(std::initializer_list<std::string>) 用两者编译 clang35 和 g++48 和打印

A::A(int, double)

正如所料。


10778
2018-01-21 01:58


起源

大概你的意思是Clang 3.5。你命名二进制文件的确不是很有用:) mv clang25 clang35 “哎呀呀” - Lightness Races in Orbit
你是对的:)这是团队维护构建系统在工作中使用的版本控制约定,我从来没有考虑过它。 - Pradhan


答案:


这种行为是有道理的。 Scott Meyers在Effective Modern C ++中有一个与此类似的例子(强调原文):

但是,如果一个或多个构造函数声明了类型的参数 std::initializer_list,使用支撑初始化语法的调用非常喜欢重载 std;:initializer_list秒。 非常。如果有的话 无论如何 对于编译器来说,使用一个支撑的初始化器将一个调用解释为一个构造函数 std::initializer_list,编制者将采用这种解释。

使用此类的示例:

class Widget {
public:
    Widget(int, bool);
    Widget(int, double);
    Widget(std::initializer_list<long double>);
};

Widget w1(10, true); // calls first ctor
Widget w2{10, true}; // calls std::initializer_list ctor
Widget w3(10, 5.0); // calls second ctor
Widget w4{10, 5.0}; // calls std::initializer_list ctor

那两个电话叫 initializer_list 即使他们涉及转换BOTH参数 - 尽管其他构造函数是完美的匹配。

此外:

编译器决定将支撑的初始化程序与构造函数匹配 std::initializer_lists是如此强大,即使是最佳匹配也会占上风 std::initializer_list 无法调用构造函数。例如:

class Widget {
public:
    Widget(int, bool); // as before
    Widget(int, double); // as before
    Widget(std::initializer_list<bool> ); // now bool
};

Widget w{10, 5.0}; // error! requires narrowing conversions

两个编译器都选择了正确的重载( initializer_list 一) - 我们可以看到标准(§13.3.1.7):

当非聚合类类型的对象时 T 列表初始化(8.5.4),重载决策选择构造函数   分两个阶段:

(1.1) - 最初,候选函数是类的初始化列表构造函数(8.5.4) T 和   参数列表由初始化列表作为单个参数组成。
  (1.2) - 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中   候选函数是该类的所有构造函数 T 参数列表由元素组成   初始化列表。

但是调用那个特定的构造函数涉及到缩小范围。在8.5.1中:

如果 初始化子句 是一种表达方式   转换表达式需要缩小转换(8.5.4),程序格式不正确。

所以该计划是不正确的。在这种情况下,当gcc选择发出警告时,clang选择抛出错误。两个编译器都符合要求。


11
2018-01-21 02:05



哪种行为有意义? clang和gcc不同意 - clang 3.5无法编译,而g ++ 4.8.3选择了 initializer_list 即使存在缩小的转换,也会过载。上面的例子没有说明任何困难 - 转换并没有缩小,因而也没有 initilalizer_list 选择超载并不奇怪。 - Pradhan
我们可以补充一点 强烈的偏好 很棒,因为那样你就可以 std::vector<std::string> { "hello", "world" } 因为编译器 努力 尝试将初始化列表视为类型对象的初始值设定项 std::string。否则我们会永远陷入困境 std::vector<std::string> { std::string{"hello"}, std::string{"world"} }。 想象一下当你尝试初始化某些东西时的痛苦 不那么琐碎。 - sehe
@Pradhan在书中添加了缩小范例 - Barry
@Barry两个编译器都符合要求。缩小使程序 病态的,这只意味着编译器必须发出诊断;海湾合作委员会的警告符合这一要求。 - T.C.
@ T.C。我看到很多明确说明的例子 // error: narrowing - Barry