12

以下无法编译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 作为唯一参数或作为第一个参数(如果其余参数具有默认值)的构造函数都将被检查,并通过重载决议与 std::initializer_list 类型的单个参数进行匹配

如果前一阶段没有产生匹配,则 T 的所有构造函数都参与重载决议,以对抗由括号初始化列表的元素组成的参数集,并限制只允许非缩小转换。如果此阶段生成显式构造函数作为复制列表初始化的最佳匹配,则编译失败(注意,在简单复制初始化中,根本不考虑显式构造函数)

由于不允许缩小转换,我希望重载解析步骤与构造函数不匹配A(std::initializer_list<int>),而是与构造函数匹配A(int, double)。例如,更改A(std::initializer_list<int>)为同时使用and和 print进行A(std::initializer_list<std::string>)编译clang35g++48

A::A(int, double)

正如预期的那样。

4

1 回答 1

11

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

但是,如果一个或多个构造函数声明一个类型为 的参数std::initializer_list,则使用大括号初始化语法的调用强烈倾向于采用 s 的重载std;:initializer_list强烈。如果编译器有任何方法可以将使用大括号初始化程序的调用解释为采用 a 的构造函数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_listctor,即使它们涉及转换两个参数——即使其他构造函数是完美匹配的。

此外:

编译器将大括号初始值设定项与采用 s 的构造函数匹配的决心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) — 最初,候选函数是类的初始化列表构造函数 (8.5.4),T参数列表由初始化列表作为单个参数组成。
(1.2) — 如果没有找到可行的初始化列表构造函数,则再次执行重载决议,其中候选函数是类的所有构造函数,T参数列表由初始化列表的元素组成。

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

如果initializer-clause是一个表达式并且需要一个窄化转换 (8.5.4) 来转换该表达式,则程序是非良构的。

所以程序格式不正确。在这种情况下,clang 选择抛出错误,而 gcc 选择发出警告。两个编译器都符合要求。

于 2015-01-21T02:05:15.993 回答