initializer_list 构造函数的列表初始化和重载解析失败
List-initialization and failed overload resolution of initializer_list constructor
以下无法使用 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
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
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
进行编译并打印
A::A(int, double)
符合预期。
这种行为是有道理的。 Scott Meyers 在 Effective Modern C++ 中有一个几乎与此完全相同的示例(强调原文):
If, however, one or more constructors declare a parameter of type std::initializer_list
, calls using the braced initialization syntax strongly prefer the overloads taking std;:initializer_list
s. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking a std::initializer_list
, compilers will employ that interpretation.
使用此 class 的示例:
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
构造函数,即使它们涉及转换两个参数 - 即使其他构造函数是完美匹配。
此外:
Compilers' determination to match braced initializers with constructors taking std::initializer_list
s is so strong, it prevails even if the best-match std::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) 所要求的:
When objects of non-aggregate class type T
are list-initialized (8.5.4), overload resolution selects the constructor
in two phases:
(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 中:
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 选择发出警告。两个编译器都符合要求。
以下无法使用 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
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
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
进行编译并打印
A::A(int, double)
符合预期。
这种行为是有道理的。 Scott Meyers 在 Effective Modern C++ 中有一个几乎与此完全相同的示例(强调原文):
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 的示例:
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
构造函数,即使它们涉及转换两个参数 - 即使其他构造函数是完美匹配。
此外:
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) 所要求的:
When objects of non-aggregate class type
T
are list-initialized (8.5.4), overload resolution selects the constructor in two phases:(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 classT
and the argument list consists of the elements of the initializer list.
但是调用那个特定的构造函数涉及缩小。在 8.5.1 中:
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 选择发出警告。两个编译器都符合要求。