初始化初始化程序和构造方法的列表初始化和失败的重载解析
以下内容无法使用clang35 -std=c++11
进行编译:
The below fails to compile with 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
OTOH, it warns about the narrowing and compiles on 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
Does either behaviour make sense? Quoting from cppreference
所有将std :: initializer_list作为唯一参数的构造函数, 或作为第一个参数(如果其余参数具有默认值) 值进行检查,并通过过载解析来匹配 类型为std :: initializer_list
All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list
如果上一个阶段不产生匹配项,则T的所有构造函数 参与针对一组参数的重载解析 由braced-init-list的元素组成,但有限制 仅允许非缩小转换.如果这个阶段 产生一个显式构造函数,作为与之的最佳匹配 复制列表初始化,编译失败(请注意,简单 复制初始化,根本不考虑显式构造函数)
If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)
由于不允许缩小转换,我希望重载解析步骤不匹配A(std::initializer_list<int>)
构造函数,而是匹配A(int, double)
构造函数.例如,将A(std::initializer_list<int>)
更改为A(std::initializer_list<std::string>)
时,将同时使用clang35
和g++48
进行编译并打印
Since narrowing conversions aren't allowed, I would expect the overload resolution step to not match the A(std::initializer_list<int>)
constructor and instead match the A(int, double)
one. For example, changing A(std::initializer_list<int>)
to A(std::initializer_list<std::string>)
compiles with both clang35
and g++48
and prints
A::A(int, double)
符合预期.
这种行为是有道理的.斯科特·迈耶斯(Scott Meyers)在《有效的现代C ++》(最初强调)中有一个几乎完全像这样的例子:
The behavior makes sense. Scott Meyers has an example almost exactly like this in Effective Modern C++ (emphasis in original):
但是,如果一个或多个构造函数声明了一个类型为
std::initializer_list
的参数,则使用大括号初始化语法的调用强烈希望采用std;:initializer_list
的重载. 非常.如果编译器有任何方式将支撑式初始化程序构造为使用std::initializer_list
的构造函数,则编译器将采用该解释.
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax strongly prefer the overloads takingstd;:initializer_list
s. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking astd::initializer_list
, compilers will employ that interpretation.
使用此类的示例:
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,即使它们涉及转换两个参数-即使其他构造函数都是完美的匹配.
Those two calls call the initializer_list
ctor even though they involve converting BOTH arguments - and even though the other constructors are perfect matches.
此外:
编译器确定将带括号的初始值设定项与采用
std::initializer_list
s的构造函数进行匹配的决心是如此强大,即使无法调用最佳匹配的std::initializer_list
构造函数,这种决定也很普遍.例如:
Compilers' determination to match braced initializers with constructors taking
std::initializer_list
s is so strong, it prevails even if the best-matchstd::initializer_list
constructor can't be called. For example:
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节)中看到这是必需的:
Both compilers pick the correct overload (the initializer_list
one) - which we can see is required from the standard (§13.3.1.7):
当非聚合类类型
T
的对象被列表初始化(8.5.4)时,重载分辨率将选择构造函数 分两个阶段:
When objects of non-aggregate class type
T
are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
(1.1)-最初,候选函数是类T
的初始化器列表构造函数(8.5.4)和
参数列表由作为单个参数的初始化程序列表组成.
(1.2)—如果找不到可行的initializer-list构造函数,则再次执行重载解析,其中
候选函数是类T
的所有构造函数,参数列表由元素组成
初始化列表的列表.
(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T
and the
argument list consists of the initializer list as a single argument.
(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the
candidate functions are all the constructors of the class T
and the argument list consists of the elements
of the initializer list.
但是调用该特定构造函数会涉及到范围缩小.在8.5.1中:
But calling that particular constructor involves a narrowing. In 8.5.1:
如果 initializer-clause 是一个表达式 并且要转换表达式需要缩小转换(8.5.4),程序格式不正确.
If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.
因此,程序格式错误.在这种情况下,clang选择抛出错误,而gcc选择发出警告.两种编译器都符合要求.
So the program is ill-formed. In this case, clang chooses to throw an error while gcc chooses to issue a warning. Both compilers are conforming.