初始化程序列表和运算符的RHS

mav*_*vam 41 c++ operators initializer-list c++11

我不明白为什么初始化程序列表不能用于运算符的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;
}
Run Code Online (Sandbox Code Playgroud)

最新的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};
    ^  ~~~~~~~~~~~~~~~~~~~~

    ^  ~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

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

baz << bar{1, -2, "foo", 4, 5};
Run Code Online (Sandbox Code Playgroud)

jog*_*pan 55

实际上,C++ 11的最终版本无法在二元运算符的右侧(或左侧)使用初始化列表.

首先,初始化列表不是标准§5中定义的表达式.函数的参数以及二元运算符的参数通常必须是表达式,并且§5中定义的表达式的语法不包括brace-init-lists的语法(即纯初始化列表;请注意类型名遵循通过 brace-init-list,例如bar {2,5,"hello",7}表达式).

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

§8.5.4/ 1 [...]注意:可以使用列表初始化
- 作为变量定义中的初始化程序(8.5)
- 作为新表达式(5.3.4)中的初始化程序
- 在return语句中(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[],例如mymap[{2,5,"hello"}]是合法的),最后一项允许它们在右侧赋值的一面(但不是一般的二元运算符).

二进制运营商没有这样的异常+,*或者<<,所以你不能把一个纯粹的初始化列表对它们的两侧(即不使用类型名前面一个).

至于原因, 2007年Stroustrup和Dos Reis 撰写草案/讨论文件N2215提供了很多关于初始化列表在各种情况下的问题的见解.具体来说,有一个关于二元运算符的部分(第6.2节):

考虑初始化列表的更一般用法.例如:

v = v+{3,4};
v = {6,7}+v;
Run Code Online (Sandbox Code Playgroud)

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

v = operator+(v,{3,4});
v = operator+({6,7},v);
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 一个不同的符号可能会使更多事情成为可能,但`{}`是数组初始化器和从C89继承的POD结构初始化器的自然扩展. (2认同)
  • 谢谢你的解释 - 我搜索了tenary运算符`true?{1,2,3}:{4,5,6}` - 所以这不仅仅是二元运算符的问题...... (2认同)
  • @jogojapan 你认为这个推理——因为我们不能两者兼得而禁止 RHS 是合理的吗?这难道不是“至善至美”的事例吗?毕竟,它的强大功能仅在 RHS 下仍然有用。例如,对于 OP 示例 RHS 就足够了,交换参数甚至没有意义! (2认同)
  • @jogojapan 是的,我不认为 RHS 只是“杂七杂八”——那里更经常需要扣除。我已经开始讨论,希望重新审视严酷的规则 https://groups.google.com/a/isocpp.org/d/msg/std-proposals/nXjimf0amus/nd-ilyXnFwAJ (2认同)