为什么采用 std::initializer_list 的构造函数不首选双花括号语法

Why wasn't a double curly braces syntax preferred for constructors taking a std::initializer_list

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

统一初始化是c++ 11的一个重要而有用的特性。但是,您不能在任何地方都使用{},因为:

std::vector<int> a(10, 0);    // 10 elements of value zero
std::vector<int> b({10, 0});  // 2 elements of value 10 and 0 respectively
std::vector<int> c{10, 0};    // 2 elements of value 10 and 0 respectively
std::vector<int> d = {10, 0}; // 2 elements of value 10 and 0 respectively
auto e(0);    // deduced type is int
auto f = 0;   // deduced type is int
auto g{0};    // deduced type is std::initializer_list<int>
auto h = {0}; // deduced type is std::initializer_list<int>

注意到聚合初始化例如std::arrays需要使用{{}},在我看来,将选择哪个向量构造函数的整个问题可以通过要求{{}}调用具有std::initializer_list的构造函数来避免:

std::vector<int> i{10, 0};    // 10 elements of value zero
std::vector<int> j{{10, 0}};  // 2 elements of value 10 and 0 respectively
std::vector<int> k = {10, 0}; // 2 elements of value 10 and 0 respectively
auto l{0};    // deduced type is int
auto m{{0}};  // deduced type is std::initializer_list<int>
auto n = {0}; // deduced type is std::initializer_list<int>

我肯定这是讨论过的,那么反对的理由是什么?最好引用标准提案中的引用/链接作为答案。

更新。 - N2532中有一点指出:

(3)可能出现的严重歧义情况只发生在较短的初始化器列表中[…]

(5)为什么语言规则要强迫那些想要简洁的程序员和歧义控制(有很好的理由)写更多请喜欢(有充分理由)更多的程序员明确的——可以吗?

[…]

假设程序员希望调用f(X)。f(Y)"劫持"一个电话?

(4)假设X没有初始化列表构造函数,但Y有。在在这种情况下,给予初始化列表构造函数的优先级更合适劫机者(还记得我们假设程序员期望f(X)被调用。这类似于某人期待f(y)使用用户定义的转换调用f(X),然后有人来了以及一个完全匹配的f(Y)我认为这是公平的期望使用{…}的人会记住的可能性初始化器列表构造函数。

我猜关键在于可以是,这意味着你不必使用统一的初始化。正确使用{}是困难的,因为:
  • 你不仅要检查你想要调用的构造函数,而且要检查任何构造函数是否有可能赢得(很可能)initializer_list;

  • 如果您使用{}编写代码,并且将来有人添加std::initializer_list构造函数,那么您的代码可能会中断,并且会静默

即使你有一个类AA(int, bool)A(std::initializer_list<double>)的构造函数,后者将被选择在前者的A a{0, false}; (IMO是坚果),所以我发现它真的很难使用统一的初始化类,或可能有(水晶球超能力需要)initializer_list构造函数。

你的代码可以无声地中断,这让我很担心。

以下是Stroustrup对这个主题的看法:

统一和通用的设计不仅仅是第四种选择。它被设计为初始化语法,不幸的是[不]适用于所有遗留代码,特别是vector。如果我今天设计vector,你可能不得不说类似vector<int> {Count{9}};的东西来获得计数。

在回答"问题是向量还是{}-init语法?"

这是矢量设计:如果我今天设计vector,你可能不得不说vector<int> {Count{9}};之类的东西来获得计数。

更普遍的问题是,有几个语义上不同的相同类型的参数最终会导致混淆,特别是当它们可能以形容词的形式出现时。例如:

vector<int> v(7,2);    // 7 (a count) element with the value 2

(这不是真正的答案,只是讨论我对这个问题的看法。)

我想我希望编译器在存在歧义的情况下给出警告,建议开发人员使用({ })(如果他们确实想要initializer_list)或( )(如果他们不需要)。如果MostVexingParse有风险,会有一个额外的警告!-也许推荐(( ))来避免这种情况?

(下面的"故事"可能不是基于该功能开发的正确历史年表,但这是我对编译器当前规则的理解。)

一开始我们有构造函数:

type t (...);

然后我们有了允许{给出一个用于构造函数(但也在其他地方)的文字集合的想法。

type t ( {...} );

…以及在构造函数中一个新的initializer_list类型来匹配它。

然后,我们被允许用{ }替换( ),以避免最令人烦恼的解析:

type t { ... };
type t { {...} };

到目前为止,一切顺利。语言的纯扩展。

最后,"有争议的"补充是,当编译器看到{ ... }(作为构造函数)时,它将首先尝试将其重写为({ ... })(如果存在则调用initializer_list),然后再返回到( ... )。我想我更希望这两种选择都被认为是同样好的,如果两种选择都有可能的话,会有一个警告或错误。