【问题标题】:Initializer lists and RHS of operators初始化器列表和运算符的 RHS
【发布时间】:2017-06-30 16:57:59
【问题描述】:

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

?

【问题讨论】:

  • 因为您没有在 RHS 上重载 operator&lt;&lt; 以获取 initializer_list&lt;&gt;...您的实际问题是什么?
  • 我希望这相当于baz &lt;&lt; bar{1, 2, 3, 4, 5};,但似乎没有发生转换。
  • 如果这是您想要的行为,也许您应该尝试为 bar 提供一个非显式构造函数,该构造函数采用单个 initializer_list&lt;&gt;
  • 这行不通,因为 initializer_list恰好一个模板参数,而不是具有多种参数类型的可变参数构造函数。
  • 这很有趣:operator&lt;&lt;(baz, {1, -2, "foo", 4, 5}); 确实有效。

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


【解决方案1】:

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

首先,initializer-lists 不是表达式,如标准第 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&lt;&lt;(baz, {1, -2, "foo", 4, 5}); 起作用的原因),第五项允许它在下标表达式中(即作为operator[] 的参数,例如mymap[{2,5,"hello"}] 是合法的) , 最后一项允许它们位于 assignments 的右侧(但不是一般的二元运算符)。

二元运算符没有这样的例外,例如 +*&lt;&lt;,因此您不能放置纯初始化列表(即前面没有typename) 在它们的任一侧。

至于原因,Stroustrup 和 Dos Reis 于 2007 年撰写的 draft/discussion paper 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;。我们怀疑允许初始化列表作为右手参数,但不允许 [原文如此] 作为大多数运算符的左手参数,这太过分了,[...]

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

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

【讨论】:

  • 不同的符号可能使更多事情成为可能,但{} 是继承自 C89 的数组初始化程序和 POD 结构初始化程序的自然扩展。
  • 感谢您的解释 - 我搜索了三元运算符 true?{1,2,3}:{4,5,6} - 所以这不仅仅是二元运算符的问题......
  • @PiotrNycz 这可能不是解析问题,而是类型推导问题。三元运算符的两种选择都需要就一个共同的类型达成一致,并且还必须确定结果值类别。大括号模糊了程序的含义。在?: 中最好是明确的并将类型名称放在{ 之前,这不会牺牲任何表达能力。
  • @jogojapan 你认为这个理由——因为我们不能同时拥有两者而禁止 RHS 是合理的吗?这难道不是一个“善中之完美”的案例吗?毕竟,它的强大功能仍然有用 RHS-only。例如,对于 OP 示例 RHS 就足够了,交换 args 甚至没有意义!
  • @jogojapan 是的,我不认为 RHS 只是“杂牌”——那里更经常需要扣除。我已经开始讨论,希望重新审视严厉的规则groups.google.com/a/isocpp.org/d/msg/std-proposals/nXjimf0amus/…
猜你喜欢
  • 1970-01-01
  • 2012-03-11
  • 2014-11-20
  • 2019-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多