48

我不明白为什么不能在运算符的 RHS 上使用初始化列表。考虑:

class foo { };

struct bar
{
    template<typename... T>
    bar(T const&...) { }
};

foo& operator<<(foo& f, bar const&) { return f; }

int main()
{
    foo baz;
    baz << {1, -2, "foo", 4, 5};

    return 0;
}

最新的 Clang(以及 gcc)抱怨:

clang.cc:14:9: error: initializer list cannot be used on the right hand side of operator '<<'
    baz << {1, -2, "foo", 4, 5};
    ^  ~~~~~~~~~~~~~~~~~~~~

    ^  ~~~~~~~~~~~~~~~

为什么 C++ 标准会禁止这样做?或者换句话说,为什么这会失败而不是

baz << bar{1, -2, "foo", 4, 5};

?

4

1 回答 1

62

事实上,C++11 的最终版本不允许在二元运算符的右侧(或左侧)使用初始化列表。

首先,初始化列表不是标准第 5 节中定义的表达式。函数的参数以及二元运算符的参数通常必须是表达式,第 5 节中定义的表达式语法不包括大括号初始化列表的语法(即纯初始化列表;注意后面的类型名通过一个大括号初始化列表,例如bar {2,5,"hello",7}是一个表达式)。

为了能够方便地使用纯初始化器列表,标准定义了各种异常,这些异常总结在以下(非规范)注释中:

§8.5.4/1 [...] 注意:列表初始化可以用作
变量定义 (8.5)
中的初始化程序 — 作为新表达式 (5.3.4) 中的初始化程序
— 在返回语句 (6.6 .3)
— 作为函数参数 (5.2.2)
— 作为下标 (5.2.1)
— 作为构造函数调用的参数 (8.5, 5.2.3)
— 作为非静态数据成员的初始化器 (9.2 )
— 在 mem-initializer (12.6.2) 中
— 在赋值 (5.17) 的右侧
[...]

上面的第四项明确允许纯初始化列表作为函数参数(这就是为什么operator<<(baz, {1, -2, "foo", 4, 5});有效),第五项允许它在下标表达式中(即作为operator[], eg的参数mymap[{2,5,"hello"}]是合法的),最后一项允许它们在右侧赋值的一面(但不是一般的二元运算符)。

对于像, or之类的二元运算符没有这样的例外,因此您不能在它们的任一侧放置一个纯初始化列表(即前面没有类型名的列表)。+*<<

至于造成这种情况的原因, Stroustrup 和 Dos Reis 从 2007 年开始的草稿/讨论论文 N2215提供了很多关于初始化列表在各种上下文中的许多问题的见解。具体来说,有一节是关于二元运算符的(第 6.2 节):

考虑初始化器列表的更一般用途。例如:

v = v+{3,4};
v = {6,7}+v;

当我们将运算符视为函数的语法糖时,我们自然会认为上述等价于

v = operator+(v,{3,4});
v = operator+({6,7},v);

因此,将初始化列表的使用扩展到表达式是很自然的。初始化器列表与运算符结合的许多用途是一种“自然”表示法。
然而,编写一个允许任意使用初始值设定项列表的 LR(1) 文法并非易事。块也以 { 开头,因此允许初始化列表作为表达式的第一个(最左边)实体会导致语法混乱。
允许初始化列表作为二元运算符的右手操作数,在下标和语法的类似隔离部分中是微不足道的。真正的问题是允许;a={1,2}+b;作为赋值语句而不允许;{1,2}+b;. 我们怀疑允许初始化列表作为右手参数,但不允许 [原文如此] 作为大多数运算符的左手参数,这太过分了,[...]

换句话说,初始化列表没有在右侧启用,因为它们在左侧没有启用,并且它们在左侧没有启用,因为这对解析器构成了太大的挑战.

我想知道是否可以通过为初始化列表语法选择不同的符号而不是花括号来简化问题。

于 2012-07-12T06:10:06.090 回答