操作符的初始化列表和 RHS

2021-12-12 00:00:00 operators c++ c++11 initializer-list

我不明白为什么不能在操作员的 RHS 上使用初始化列表.考虑:

I do not understand why initializer lists cannot be used on the RHS of an operator. Consider:

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)抱怨:

The latest Clang (gcc as well) complains:

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

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

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

Why would the C++ standard forbid this? Or put differently, why does this fail as opposed to

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

?

推荐答案

C++11 的最终版本确实不允许在右侧(或左侧,就此而言)使用初始化列表) 的二元运算符.

Indeed the final version of C++11 does not enable the use of initializer lists on the right-hand side (or left-hand side, for that matter) of a binary operator.

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

Firstly, initializer-lists are not expressions as defined in §5 of the Standard. The arguments of functions, as well as of binary operators, generally have to be expressions, and the grammar for expressions defined in §5 does not include the syntax for brace-init-lists (i.e. pure initializer-lists; note that a typename followed by a brace-init-list, such as bar {2,5,"hello",7} is an expression, though).

为了能够方便地使用纯初始化列表,标准定义了各种异常,总结如下(非规范):

In order to be able to use pure initializer-lists conveniently, the standard defines various exceptions, which are summarized in the following (non-normative) note:

§8.5.4/1[...] 注意:可以使用列表初始化
― 作为变量定义中的初始值设定项 (8.5)
― 作为 new 表达式中的初始化器 (5.3.4)
― 在 return 语句中 (6.6.3)
― 作为函数参数 (5.2.2)
― 作为下标 (5.2.1)
― 作为构造函数调用的参数 (8.5, 5.2.3)
― 作为非静态数据成员的初始值设定项 (9.2)
― 在内存初始化程序中 (12.6.2)
― 在作业的右侧 (5.17)
[...]

§8.5.4/1 [...] Note: List-initialization can be used
― as the initializer in a variable definition (8.5)
― as the initializer in a new expression (5.3.4)
― in a return statement (6.6.3)
― as a function argument (5.2.2)
― as a subscript (5.2.1)
― as an argument to a constructor invocation (8.5, 5.2.3)
― as an initializer for a non-static data member (9.2)
― in a mem-initializer (12.6.2)
― on the right-hand side of an assignment (5.17)
[...]

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

The fourth item above explicitly allows pure initializer-lists as function arguments (which is why operator<<(baz, {1, -2, "foo", 4, 5}); works), the fifth one allows it in subscript expressions (i.e. as argument of operator[], e.g. mymap[{2,5,"hello"}] is legal), and the last item allows them on the right-hand side of assignments (but not general binary operators).

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

There is no such exception for binary operators like +, * or <<, hence you can't put a pure initializer list (i.e. one that is not preceded with a typename) on either side of them.

至于原因,草案/讨论文件 N2215 由 Stroustrup 和 Dos Reis 于 2007 年撰写,提供了对各种上下文中初始化列表的许多问题的深入见解.具体来说,有一节是关于二元运算符的(第 6.2 节):

As to the reasons for this, a draft/discussion paper N2215 by Stroustrup and Dos Reis from 2007 provides a lot of insight into many of the issues with initializer-lists in various contexts. Specifically, there is a section on binary operators (section 6.2):

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

Consider more general uses of initializer lists. For example:

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

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

When we consider operators as syntactic sugar for functions, we naturally consider the above equivalent to

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

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

It is therefore natural to extend the use of initializer lists to expressions. There are many uses where initializer lists combined with operators is a "natural" notation.
However, it is not trivial to write a LR(1) grammar that allows arbitrary use of initializer lists. A block also starts with a { so allowing an initializer list as the first (leftmost) entity of an expression would lead to chaos in the grammar.
It is trivial to allow initializer lists as the right-hand operand of binary operators, in subscripts, and similar isolated parts of the grammar. The real problem is to allow ;a={1,2}+b; as an assignment-statement without also allowing ;{1,2}+b;. We suspect that allowing initializer lists as right-hand, but nor [sic] as left-hand arguments to most operators is too much of a kludge, [...]

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

In other words, initializer-lists are not enabled on the right-hand side because they are not enabled on the left-hand side, and they are not enabled on the left-hand side because that would have posed too big a challenge for parsers.

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

I wonder if the problem could have been simplified by picking a different symbol instead of curly braces for the initializer-list syntax.

相关文章