10

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

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 中的错误还是我们需要额外的括号?

4

2 回答 2

10

这是 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_assert)并在内部归档。

20180205 更新:此错误已针对 Visual C++ 的未来版本进行了修复。

于 2018-02-04T19:01:22.607 回答
4

有趣的问题。

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

我想这是因为在 C++17 的 n4659 草案中(抱歉:我无法访问最终版本)我看到了除法运算符涉及“乘法表达式”的表达式规则(A.4)规则如下

乘法表达式/ pm 表达式

乘法表达式”也可以是“ 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

形式为 (... op e) 的表达式,其中op折叠运算符,称为一元左折叠

所以我以为

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

应该被实例化

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

无论如何......正确的MSVC......可以肯定......你想要

return (1 + 3) / 2.0;

我建议您添加另外几个括号。

于 2018-02-04T16:00:08.197 回答