初始化列表和运算符的 RHS

2024-01-24

我不明白为什么初始化列表不能在运算符的 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};

?


事实上,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)
— 在内存初始化器中 (12.6.2)
— 在作业的右侧 (5.17)
[...]

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

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

至于这样做的原因, a 草稿/讨论文件 N2215 http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2007/n2215.pdfStroustrup 和 Dos Reis 于 2007 年撰写的文章提供了对各种上下文中初始化列表的许多问题的深入见解。具体来说,有一节是关于二元运算符的(第 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;。我们怀疑,允许初始化列表作为右侧参数,但也不允许 [原文如此] 作为大多数运算符的左侧参数,这太过于混乱,[...]

换句话说,右侧未启用初始化列表因为它们在左侧未启用,并且它们在左侧未启用,因为这会给解析器带来太大的挑战。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

初始化列表和运算符的 RHS 的相关文章

随机推荐