C++ MSVC中的折叠表达式

C++ MSVC中的折叠表达式,c++,language-lawyer,variadic-templates,c++17,fold-expression,C++,Language Lawyer,Variadic Templates,C++17,Fold Expression,我有以下计算平均值的函数: template<typename... Ts> auto mean_of(const Ts... values) { return (... + values) / static_cast<double>(sizeof...(Ts)); } 模板 (常数…值)的自动平均值 { 返回(…+值)/static_cast(sizeof…(Ts)); } VS 2017 15.6.0预览版3包含以下代码 std::cout <<

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

template<typename... Ts>
auto mean_of(const Ts... values)
{
    return (... + values) / static_cast<double>(sizeof...(Ts));
}
模板
(常数…值)的自动平均值
{
返回(…+值)/static_cast(sizeof…(Ts));
}
VS 2017 15.6.0预览版3包含以下代码

std::cout << mean_of(1, 3);
std::cout有趣的问题

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

我想这是因为在C++17的n4659草案中(抱歉:我在最终版本中没有访问权限),我看到了表达式规则(A.4),其中除法运算符包含在“乘法表达式”规则中,如下所示

乘法表达式/pm表达式

“乘法表达式”也可以是“pm表达式”,可以是“强制转换表达式”,可以是“一元表达式”,可以是“后缀表达式”,可以是“主表达式”,可以是“折叠表达式”

因此,这条规则可以被视为

折叠表达式/pm表达式

因此,如果我没有错的话,“折叠表达式”应该在应用除法之前作为一个整体进行计算

我的第一个解释(MSVC right、g++和clang++Error)是基于17.5.3的仓促演讲

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

(9.1)((E1 op E2)op···)op EN表示一元左折

和8.1.6

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

所以我想

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

我建议您再添加两个括号。

这是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);
模板
常量表达式自动f1(Ts常量…VAL){
返回0*(VAL+…);
}
模板
常量表达式自动f2(Ts常量…VAL){
返回值(VAL+…)*0;
}
静态断言(f1(1,2,3)==0);
静态断言(f1(1,2,3)!=0*1+(2+3));
静态断言(f2(1,2,3)==0);
静态断言(f2(1,2,3)!=1+(2+3)*0);
(与和一起编译很好,但在MSVC中触发所有四个
static\u assert
s)并在内部归档


20180205更新:这个bug已经被固定在将来的VisualC++中。

有趣的解释,但是另一方面,语法说整个折叠表达式是分割表达式的一个子表达式…@ RaKeE1111——是的:它对我也是反直觉的;希望是错误的,如果我是对的,我认为这是一个缺陷。没有什么特别的-只是使用语法进行解析会生成一个特别的语法树,并且由于语义的原因,通常不会重新排列该树。但是我同意这个标准是不明确的:“实例化产生”是指代词的替换和语法的重新转换,还是仅仅表示表达式的语义就好像它是这个表达式一样?@ArneVogel-确切地说:外圆括号是折叠表达式的强制部分,但是,在相应的产品中,外部缺少的是促使我第一次(现在我认为是错误的)解释的一点。如果MSVC在这里,标准将是错误的。一个相关的混淆:函数参数包的一个参数是
decltype(x op…
的行为是什么@Aschepper我对这个非常聪明的例子的看法是,既然折叠表达式(从语法上讲)不是id表达式,那么不管expansion@aschepler要在没有相同语法陷阱的情况下展示,也许在精神上更接近于original@LucDanton这两件事对我来说都不那么令人惊讶。只有当内置的
+
将其子表达式强制为prvalues并生成prvalue时,才会出现差异,但如果扩展中的
+
运算符数为零,则不适用。我们不能有这样的规则“一元折叠的类型和值类别与多个参数的类型和值类别相同”,因为通常运算符的每个实例都可能是重载运算符调用,在昨天发布的15.6.0预览版4中,它不是固定的。可能,稍后会修复。
return (1 + 3) / 2.0;
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);