为什么在使用大括号初始值设定项列表时首选 std::initializer_list 构造函数

Why is the std::initializer_list constructor preferred when using a braced initializer list?

本文关键字:列表 std list initializer 构造函数 为什么      更新时间:2023-10-16

考虑代码

#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}; 
}

输出为

ctor
initializer_list ctor

据我了解,值10隐式转换为Foo(第一个ctor输出),然后初始化项构造函数启动(第二个initializer_list ctor输出)。我的问题是为什么会这样?标准构造函数不是更好的匹配Foo(int)吗?即,我本来希望这个片段的输出只是ctor.

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

§13.3.1.7 [over.match.list]/p1:

当非聚合类类型的对象T进行列表初始化时 (8.5.4),重载解析分两个阶段选择构造函数:

  • 最初,候选函数是类T的初始值设定项列表构造函数 (8.5.4),参数列表由 初始值设定项列表作为单个参数。
  • 如果未找到可行的初始值设定项列表构造函数,则再次执行重载解析,其中候选函数均为 类T和参数列表的构造函数由 初始值设定项列表的元素。

如果初始值设定项列表没有元素,而T具有默认值 构造函数,则省略第一阶段。在复制列表初始化中, 如果选择explicit构造函数,则初始化为 格式不正确。

只要存在可行的初始值设定项列表构造

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

n2100 对初始值设定项列表的建议非常详细地介绍了使序列构造函数(他们称之为采用std::initializer_lists的构造函数)优先于常规构造函数的决定。有关详细讨论,请参阅附录 B。结论中简洁地总结了一下:

11.4 结论

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

  • 在所有构造函数中寻找歧义会导致太多的"误报";也就是说,明显不相关的冲突 构造 函数。请参阅以下示例。
  • 消除歧义本身容易出错(而且冗长)。请参见 §11.3 中的示例。
  • 对同类列表中的每个元素使用完全相同的语法很重要 - 应该消除歧义 普通构造函数(没有常规模式 参数)。请参见 §11.3 中的示例。最简单的错误示例 积极是默认构造函数:

误报的最简单示例是默认构造函数:

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

在这里,优先考虑序列构造函数可以消除 为 f(1) 选择 X 是该问题的一个变体 在 §3.3 中明确显示。

整个初始值设定项列表旨在启用列表初始化,如下所示:

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

考虑一下案例

std::vector<int> v { 123 };
这打算用一个值为 123 的元素

而不是值为 0 的 123 个元素来初始化向量。

若要访问其他构造函数,请使用旧语法

Foo foo(10);