C++ 为什么在使用带括号的初始值设定项列表时首选std::initializer\u list构造函数?

C++ 为什么在使用带括号的初始值设定项列表时首选std::initializer\u list构造函数?,c++,c++11,constructor,language-lawyer,initializer-list,C++,C++11,Constructor,Language Lawyer,Initializer List,以代码为例 #include <iostream> class Foo { int val_; public: Foo(std::initializer_list<Foo> il) { std::cout << "initializer_list ctor" << std::endl; } /* explicit */ Foo(int val): val_(val) {

以代码为例

#include <iostream>

class Foo
{
    int val_;
public:
    Foo(std::initializer_list<Foo> il)
    {
        std::cout << "initializer_list ctor" << std::endl;
    }
    /* explicit */ Foo(int val): val_(val)
    {
        std::cout << "ctor" << std::endl;
    };
};

int main(int argc, char const *argv[])
{
    // why is the initializer_list ctor invoked?
    Foo foo {10}; 
}
据我所知,值
10
被隐式转换为
Foo
(首先是
ctor
输出),然后初始化器构造函数启动(第二个
initializer\u list ctor
输出)。我的问题是为什么会这样?标准构造函数
Foo(int)
不是更好的匹配吗?也就是说,我希望这个代码片段的输出只是
ctor


PS:如果我将构造函数
Foo(int)
标记为
explicit
,那么
Foo(int)
是唯一被调用的构造函数,因为整数
10
现在不能隐式转换为
Foo

整个初始值设定项列表的目的是启用列表初始化,如下所示:

std::vector<int> v { 0, 1, 2 };

§13.3.1.7[超过匹配列表]/p1:

当初始化非聚合类类型
T
的对象时 (8.5.4),过载分辨率分两个阶段选择构造函数:

  • 最初,候选函数是类
    T
    的初始值设定项列表构造函数(8.5.4),参数列表由 初始值设定项作为单个参数列出
  • 如果没有找到可行的初始值设定项列表构造函数,将再次执行重载解析,其中候选函数都是 类
    T
    的构造函数和参数列表由 初始值设定项列表的元素
如果初始值设定项列表没有元素且
T
具有默认值 在构造函数中,省略了第一阶段。在复制列表初始化中, 如果选择了
显式
构造函数,则初始化为 格式不正确

只要有一个可行的初始值设定项列表构造函数,当使用列表初始化且初始值设定项列表至少有一个元素时,它将胜过所有非初始值设定项列表构造函数。

初始值设定项列表的提案非常详细地阐述了创建序列构造函数的决策(他们称之为采用
std::initializer_list
的构造函数)比常规构造函数具有优先权。详细讨论请参见附录B。结论中对其进行了简要总结:

11.4结论

那么,我们如何在剩下的两个备选方案(“歧义”和“序列构造函数优先”)之间做出决定呢 在普通构造函数之上)?我们的建议给出了序列构造函数 优先权,因为

  • 寻找所有构造函数之间的歧义会导致太多的“误报”;也就是说,显然不相关的构造函数之间的冲突 构造函数。请参见下面的示例
  • 消除歧义本身容易出错(以及冗长)。参见§11.3中的示例
  • 对同构列表中的每一个元素使用完全相同的语法是很重要的——应该对每个元素进行消歧 普通构造函数(没有规则的 参数)。参见§11.3中的示例。最简单的错误示例 正值是默认构造函数:
最简单的误报示例是默认构造函数:

vector<int> v; 
vector<int> v { }; // potentially ambiguous
void f(vector<int>&); 
// ...
f({ }); // potentially ambiguous
向量v; 向量v{};//可能不明确 空f(向量&); // ... f({});//可能不明确
可以认为类在初始化时没有 成员在语义上与默认初始化不同,但我们 不会使语言复杂化,从而为这些问题提供更好的支持 比更常见的情况下,它们在语义上是相同的 一样

赋予序列构造函数优先级会中断参数检查 更容易理解的块和更好的局部性

void f(const vector<double>&);
// ...
struct X { X(int); /* ... */ };
void f(X);
// ...
f(1);     // call f(X); vector’s constructor is explicit
f({1});   // potentially ambiguous: X or vector?
f({1,2}); // potentially ambiguous: 1 or 2 elements of vector
void f(常数向量&);
// ...
结构X{X(int);/*…*/};
无效f(X);
// ...
f(1);//调用f(X);向量的构造函数是显式的
f({1});//可能不明确:X还是向量?
f({1,2});//可能不明确:向量的1或2个元素
在这里,优先考虑序列构造函数可以消除 来自X的干扰。选择X作为f(1)是问题的一个变体 如§3.3所示


我知道它胜过常规构造函数,但我不知道它胜过常规构造函数,即使常规构造函数更匹配。是的,这种方式似乎有点奇怪。有什么特别的原因吗?通过这种方式,可以隐藏复制构造函数(实际上,我的代码将隐藏复制构造函数,不是吗?)Scott Meyers的新书《有效的现代C++》中有一个关于各种初始化样式的非常好的条目:“条目7:在创建对象时区分()和{}”。它没有给出太多关于这种行为的理由,但确实详细介绍了一些可能会让你吃惊的边缘案例。@MichaelBurr谢谢,我仍在等待物理副本:)这很有意义,因为
initializer\u list
和常规构造函数都将
int
作为参数。我很惊讶,即使在
initializer\u list
ctor中需要转换,后者仍然是首选的。@seldon是“旧语法”从
初始值设定项列表
中删除的唯一方法。我的意思是,这是标准方法吗?好的,+1,我认为这很好!出于某种原因,我认为如果需要转换,init\u列表构造函数将不会胜出,但似乎不是这样。但是,如果是这样,那么我的代码似乎隐藏了一个潜在的副本构造函数,不是吗是吗?因为copy-ctor只会复制init-list-ctor的参数,后者可以做我想做的任何事情。@vsoftco好吧,这个规则只适用于使用列表初始化的情况。当你不使用
{}
时,copy-ctor会被很好地调用。隐式特殊成员函数确实可以被“隐藏”。常见的情况是模板构造函数可以选择跟随一个转发引用参数
Foo foo(10);
vector<int> v; 
vector<int> v { }; // potentially ambiguous
void f(vector<int>&); 
// ...
f({ }); // potentially ambiguous
void f(const vector<double>&);
// ...
struct X { X(int); /* ... */ };
void f(X);
// ...
f(1);     // call f(X); vector’s constructor is explicit
f({1});   // potentially ambiguous: X or vector?
f({1,2}); // potentially ambiguous: 1 or 2 elements of vector