Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fold expressions in MSVC

I have the following function that computes the mean value:

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

With VS 2017 15.6.0 Preview 3 the following code

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

outputs 2.5. It seems that MSVC interprets the fold expression as 1 + 3 / N and not as (1 + 3) / N. If I add extra parentheses around the fold expression, the result is correct. With GCC no extra parentheses are needed.

Is this a bug in MSVC or do we need extra parentheses?

like image 339
Evg Avatar asked Feb 04 '18 15:02

Evg


People also ask

What are fold expressions in C++?

So let us understand first what are fold expressions. This is a new feature in the C++ 17 compiler. It usually allows a user to apply the same set of binary operations to all the arguments. It works in the sequence of the first 2 arguments, then to the third, and so on… <bin_operation>args4) …… and so on.

How to implement Haskell fold expressions in C++?

With fold expressions, you can implement Haskell known functions foldl, foldr, foldl1 and foldr1 directly in C++. These four functions successively reduce a list to a single value. C++11 supports variadic templates. These are templates that can accept an arbitrary number of template arguments. The arbitrary number is held by a parameter pack.

What is the instantiation of a fold expression?

The instantiation of a fold expression expands the expression e as follows: template<typename ... Args> bool all ( Args... args) { return ( ... && args); } bool b = all (true, true, true, false); // within all (), the unary left fold expands as // return ( (true && true) && true) && false; // b is false

How to use fold expressions to reduce the parameter pack?

With fold expressions, you can directly reduce the parameter pack with the help of the binary operator. Here is the output of the program. Now to the two variations of fold expression that result in four different forms of fold expressions. At first, fold expression can have a default value. That value depends on the binary operator.


2 Answers

This is a bug in MSVC. I've reduced it to:

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);

(which compiles fine with both GCC and clang, but triggers all four static_asserts in MSVC) and filed it internally.

20180205 Update: This bug has been fixed for a future release of Visual C++.

like image 116
Casey Avatar answered Sep 28 '22 13:09

Casey


Interesting question.

Correcting my first interpretation, it seems to me that is g++ and clang++ are right and that MSVC is wrong.

I suppose this because in the draft n4659 for C++17 (sorry: I don't have access at the final version) I see the expression rules (A.4) where the division operator is involved in a "multiplicative-expression" rule as follows

multiplicative-expression / pm-expression

A "multiplicative-expression" can be also a "pm-expression" that can be a "cast-expression" that can be an "unary-expression" that can be a "postfix-expression" that can be a "primary-expression" that can be a "fold-expression"

So the rule can be seen as

fold-expression / pm-expression

So, If I'm not wrong, a "fold-expression" should be evaluated as a whole before the division is applied.

My first interpretation (MSVC right, g++ and clang++ wrong) was based over an hasty lecture of 17.5.3

The instantiation of a fold-expression produces:

(9.1) ((E1 op E2) op ···) op EN for a unary left fold

and 8.1.6

An expression on the form (... op e) where op is a fold-operator is called a unary left fold.

So I supposed that

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

should be instantiated

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

Anyway... right MSVC or not... to be sure... of you want

return (1 + 3) / 2.0;

I suggest you to add another couple of parentheses.

like image 21
max66 Avatar answered Sep 28 '22 12:09

max66