9

考虑一个例子:

#include <type_traits>

template <class... Ts>
decltype (auto) foo(Ts... ts) {
      return (ts->x + ...);
}

struct X {
    int x;
};

int main() {
    X x1{1};
    static_assert(std::is_reference_v<decltype(foo(&x1))>);
}

[现场演示]

decltype(auto)从带括号的左值推导应根据[cl.type.simple]/4.4推导为左值引用。例如:

decltype(auto) foo(X *x) { // type of result == int&
    return (x->x);
}

但是被剪断的会触发 static_assert。即使我们将表达式组合成额外的括号,例如:

return ((ts->x + ...));

它不会改变效果。

标准中是否有一点可以防止将单个元素的折叠表达式扣除到左值引用中?


编辑

作为Johannes Schaub 的一个重要观点 - litb clang 实际上确实将代码的双括号版本解释为带括号的左值并推断出左值引用。在这种情况下,我会将其解释为 gcc 错误。然而,带有单括号版本的版本仍然存在问题。令我困惑的是,至少在一个以上元素的情况下,版本必须转换为带括号的代码 - 以满足运算符优先级。例如:

(x + ...)*4 -> (x1 + x2)*4

不一致的原因是什么?

4

2 回答 2

10

如果您希望在单参数情况下返回引用,则需要一组额外的括号*以便参数包扩展扩展引用:

template <class... Ts>
decltype (auto) foo(Ts... ts) {
      return ((ts->x) + ...);
}

不幸的是,在传递多个参数的情况下,结果仍然是整数的总和,它返回一个右值,所以你的静态断言将失败。


为什么不((ts->x + ...))评估参考?

因为 fold 表达式将返回一个过期int,然后将其包装int在另一层括号中仍然是一个int. 当我们使用内括号((ts->x) + ...))时,单个参数的折叠表达式返回int&它适用于clang 6.0.0,但不适用于gcc 8.0.0,所以我不确定。

*折叠表达式的括号(涉及的括号(ts->x + ...))是折叠表达式的一部分;单独说ts->x + ...是不正确的。

于 2017-10-31T11:58:16.970 回答
0

经过一个小的调查,事实证明该标准没有为折叠表达式[temp.variadic]/9(强调我的)产生的表达式的括号提供任何明确的保证:

折叠表达式的实例化产生:

  • ((E1 op E2) op⋯) op EN 用于一元左折叠,
  • E1 op (⋯ op (EN−1 op EN)) 用于一元右折叠,
  • (((E op E1) op E2) op⋯) op EN 用于二进制左折叠,并且
  • E1 op (⋯ op (EN−1 op (EN op E))) 用于二元右折叠。

在每种情况下,op 是折叠运算符,N 是包扩展参数中的元素数量,每个 Ei 是通过实例化模式并将每个包扩展参数替换为其第 i 个元素而生成的。对于二元折叠表达式,通过实例化不包含未扩展参数包的强制转换表达式来生成 E。

例子:

template<typename ...Args>
  bool all(Args ...args) { return (... && args); }

bool b = all(true, true, true, false);

在 all 的实例化中,返回的表达式扩展为 ((true && true) && true) && false,其计算结果为 false。— 结束示例

如果对于一元折叠表达式 N 为零,则表达式的值如表 14 所示;如果运算符未在表 14 中列出,则实例化格式不正确。

这表明即使折叠表达式运算符会影响生成表达式的运算符优先级的变化,也不能保证括号,这对我来说似乎是一个标准缺陷。例如,当从标准中逐字应用示例中的规则时,(ts + ...)*4应将其扩展为ts1 + ts2*4而不是。(ts1 + ts2)*4

然而,由于在折叠表达式产生规则中没有考虑单个元素参数包,因此没有明确说明在这种情况下参数包应该产生什么,因此带括号的版本和不带括号的版本都符合标准。

于 2017-11-11T08:43:10.173 回答