初始化初始化程序和构造方法的列表初始化和失败的重载解析 [英] List-initialization and failed overload resolution of initializer_list constructor

查看:260
本文介绍了初始化初始化程序和构造方法的列表初始化和失败的重载解析的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下内容无法使用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>)时,将同时使用clang35g++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 taking std;:initializer_lists. 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 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_lists 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节)中看到这是必需的:

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.

这篇关于初始化初始化程序和构造方法的列表初始化和失败的重载解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆