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

时间:2015-01-21 01:58:46

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

以下无法使用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>)同时使用clang35g++48进行编译并打印

A::A(int, double)

正如所料。

1 个答案:

答案 0 :(得分:11)

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

  

但是,如果一个或多个构造函数声明类型为std::initializer_list的参数,则使用支撑初始化语法的调用非常喜欢使用std;:initializer_list s的重载。 强烈。如果以任何方式让编译器将使用支撑初始值设定项的调用解释为采用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 ctor,即使它们涉及转换BOTH参数 - 即使其他构造函数是完美匹配。

此外:

  

编译器将支持的初始值设定项与使用std::initializer_list的构造函数匹配的决心是如此强大,即使无法调用最佳匹配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) - 最初,候选函数是类T的初始化列表构造函数(8.5.4)和   参数列表由初始化列表作为单个参数组成   (1.2) - 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中   候选函数是类T的所有构造函数,参数列表由元素组成   初始化列表。

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

  

如果 initializer-clause 是表达式   转换表达式需要缩小转换(8.5.4),程序格式不正确。

因此该计划形成不良。在这种情况下,当gcc选择发出警告时,clang选择抛出错误。两个编译器都符合要求。