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

时间:2012-07-10 19:25:43

标签: c++ c++11 operators initializer-list

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

1 个答案:

答案 0 :(得分:53)

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

首先,初始化列表不是标准§5中定义的表达式。函数的参数以及二元运算符的参数通常必须是表达式,并且§5中定义的表达式的语法不包括brace-init-lists的语法(即纯初始化列表;请注意类型名称< em>后跟一个大括号的初始化列表,例如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[]的参数,例如{{ 1}}是合法的,最后一项允许它们位于赋值的右侧(但不是一般的二元运算符)。

对于mymap[{2,5,"hello"}]+*没有二元运算符的例外情况,因此您无法放置纯初始值设定项列表(即在它们的两边都没有一个typename)。

至于的原因,来自2007年的Stroustrup和Dos Reis的draft/discussion paper N2215提供了很多关于各种上下文中初始化列表的问题的深入见解。具体来说,有一个关于二元运算符的部分(第6.2节):

  

考虑初始化列表的更多常规用法。例如:

<<
     

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

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

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

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

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