C++11统一初始化:初始化器列表和多参数构造函数之间存在歧义



当前正试图了解C++11的统一初始化。我遇到了这个模棱两可的例子:考虑一个类,它可以由两个参数的构造函数或任何长度的初始值设定项列表构造:

class Foo {
public:
Foo(int a, int b) {
std::cout << "constructor 1" << std::endl;
}
Foo(std::initializer_list<int>) {
std::cout << "constructor 2" << std::endl;
}
};

按照统一的初始化约定,我希望以下内容能起作用:

Foo a (1, 2)打印constructor 1(duh(

Foo b {1, 2}打印constructor 1

Foo c = {1, 2}打印constructor 2

然而,编译器似乎将Foo b {1, 2}解释为列表初始化,并调用构造函数2。当存在初始值设定项列表构造函数时,()语法是否是强制编译器考虑其他类型构造函数的唯一方法?

编译器似乎将Foo b{1,2}解释为列表初始化,并调用构造函数2。((语法是唯一的方法吗当初始值设定项列表构造函数是否存在?

标准草案中的报价很好地解释了这一点:

9.4.5.2[dcl.init.list](强调矿(:

构造函数是初始值设定项列表构造函数,如果它的第一个参数类型为std​::​initializer_­list或对cv的引用std​::​initializer_­list用于某个类型E,并且要么没有其他参数或其他所有参数都有默认参数([dcl.fct.default](。

[注意2:初始化器列表构造函数在列表初始化中,优于其他构造函数([over.match.list](。将初始值设定项列表作为参数传递给类C的构造函数模板模板C(T(不创建初始化器列表构造函数,因为初始化器列表自变量导致相应的参数为非推导参数context([temp.decure.call](。--尾注]

和12.4.2.8[超过匹配列表]:

当非聚合类类型T的对象被列表初始化时[dcl.init.list]指定执行过载解析根据本款中的规则或在组建根据[over.ics.list]列出初始化顺序,重载解析分两个阶段选择构造函数:

  • 如果初始值设定项列表不为空或T没有默认构造函数,首先在候选函数处执行过载解析是类T的初始值设定项列表构造函数([dcl.init.list](参数列表由初始值设定项列表组成论点

  • 否则,或者如果找不到可行的初始值设定项列表构造函数,再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始值设定项列表的元素的。

您可以向构造函数添加一个额外的忽略参数,以在调用站点指定特定的重载,就像在STL:中一样

#include <iostream>
struct non_init_list_t {};
inline constexpr non_init_list_t non_init_list;
struct Class {
Class(int a, int b, non_init_list_t = non_init_list) { std::clog << "()n"; }
Class(std::initializer_list<int> list) { std::clog << "{}n"; }
};
Class a{12, 42, non_init_list};  // ()
Class b{12, 42};                 // {}
Class c(12, 42);                 // ()

如果构造函数有initializer_list版本,编译器将首先将其解释为initializer_list,如果没有initializer_list版本,编译器则将其解释成另一个重载版本。如果编译器将其解释为另一个版本,并且您想要调用使用initializer_list版本的构造函数,那么碰巧参数的数量和类型与其他构造函数相同,那么您将导致错误。那么编译器选择initializer_list版本还是其他版本?因此,使用括号表示法绝对不是initializer_list版本。如果您的构造函数中没有initializer_list版本,那么不要担心这个问题。

顺便说一句,如果您使用auto自动推断类型,请不要使用统一初始化。它必须将类型解释为initializer_list

最新更新