【问题标题】:Fold expressions in MSVCMSVC 中的折叠表达式
【发布时间】:2018-02-04 15:09:42
【问题描述】:

我有以下计算平均值的函数:

template<typename... Ts>
auto mean_of(const Ts... values)
{
    return (... + values) / static_cast<double>(sizeof...(Ts));
}

使用 VS 2017 15.6.0 Preview 3 以下代码

std::cout << mean_of(1, 3);

输出2.5。似乎 MSVC 将折叠表达式解释为 1 + 3 / N 而不是 (1 + 3) / N。如果我在折叠表达式周围添加额外的括号,结果是正确的。使用 GCC,不需要额外的括号。

这是 MSVC 中的错误还是我们需要额外的括号?

【问题讨论】:

  • 一个相关的困惑:decltype(x op ...) 的行为是什么,带有一个函数参数包的参数? coliru.stacked-crooked.com/a/20448cc227c873fa
  • @aschepler 我对这个非常聪明的案例的看法是,由于折叠表达式不是(从语法上讲)一个 id-expression 那么decltype should follow non-entity rules 不管扩张
  • @aschepler a sizeof variant 可能展示a [temp.variadic] rule 没有相同的语法陷阱
  • yet another variant,或许在精神上更接近原作
  • @LucDanton 这些都没有让我感到惊讶。之所以会出现差异,只是因为内置的+ 将其子表达式强制为纯右值并产生纯右值,但如果扩展中+ 运算符的数量为零,则不适用。而且我们不能有一个规则,比如“一个参数的一元折叠的类型和值类别就是多个参数的类型”,因为通常每个运算符实例都可能是一个重载的运算符调用,这可能导致任何价值类别和任何可能的类型。

标签: c++ language-lawyer variadic-templates c++17 fold-expression


【解决方案1】:

这是 MSVC 中的一个错误。我将其简化为:

template<class... Ts>
constexpr auto f1(Ts const... vals) {
    return 0 * (vals + ...);
}

template<class... Ts>
constexpr auto f2(Ts const... vals) {
    return (vals + ...) * 0;
}

static_assert(f1(1,2,3) == 0);
static_assert(f1(1,2,3) != 0 * 1 + (2 + 3));
static_assert(f2(1,2,3) == 0);
static_assert(f2(1,2,3) != 1 + (2 + 3) * 0);

(与GCCclang 都可以正常编译,但会在MSVC 中触发所有四个static_asserts)并在内部归档。

20180205 更新:此错误已在 Visual C++ 的未来版本中修复。

【讨论】:

  • 在昨天发布的 15.6.0 Preview 4 中,它没有被修复。可能,稍后会修复。
【解决方案2】:

有趣的问题。

纠正我的第一个解释,在我看来 g++ 和 clang++ 是对的,而 MSVC 是错的。

我想这是因为在 C++17 的 n4659 草案中(对不起:我在最终版本中没有访问权限)我看到了除法运算符涉及“multiplicative-expression" 规则如下

乘法表达式 / pm-表达式

一个“multiplicative-expression”也可以是一个“pm-expression”,它可以是一个“cast-expression”,它可以可以是“unary-expression”,可以是“postfix-expression”,可以是“primary-expression”,可以是"折叠表达式"

所以规则可以看成是

折叠表达式 / pm-表达式

所以,如果我没记错的话,应该在应用除法之前将“折叠表达式”作为一个整体进行评估。

我的第一个解释(MSVC 正确,g++ 和 clang++ 错误)基于 17.5.3 的仓促讲座

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

(9.1) ((E1 op E2) op ···) op EN 用于一元左折叠

和 8.1.6

opfold-operator 的形式 (... op e) 上的表达式称为 unary左折叠

原来如此

return (... + values) / static_cast<double>(sizeof...(Ts));

应该被实例化

return ((v1 + v2) + ... ) + vn / static_cast<double>(sizeof...(Ts));

无论如何...正确的 MSVC 与否...确定...你想要的

return (1 + 3) / 2.0;

我建议你再添加一对括号。

【讨论】:

  • 有趣的解释,但另一方面,语法表明整个折叠表达式是除法表达式的子表达式...
  • @Rakete1111 - 是的:这对我来说也是违反直觉的;希望是错的,如果我是对的,我认为这是一个缺陷。
  • 没什么特别的——只是使用语法进行解析会生成特定的语法树,并且由于语义,您通常不会重新排列该树。但我确实同意标准不清楚:“实例化产生”是否意味着替换标记和句法重新解析,或者只是表达式的语义好像是这个表达式?
  • @ArneVogel - 确切地说:外部括号是 fold-expression 的强制性部分,但在对应的生产中外部不存在,这是驱使我的观点我的第一个(现在我认为是错误的)解释。
  • 如果 MSVC 是正确的,标准将是错误的。
猜你喜欢
  • 2023-03-24
  • 2021-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-20
相关资源
最近更新 更多