Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is value returned by lambda using a static local wrong in MSVC2017 15.9.3 with /std:c++17?

The example code below prints values from a lambda function, which simply increments and returns the value of a static local counter variable.

It prints 0,1 and 2,3 as expected with gcc and clang with C++17. But not in Visual Studio Community 2017 15.9.3 with /std:c++17 set - it prints 0,0 and 2,3 instead.

#include <iostream>

int main() {
    auto f = [] {
        static int i = 0;
        return i++;
    };
    const int v1 = f(); // Expect v1 = 0
    const int v2 = f(); // Expect v2 = 1

    // Prints the wrong values (MSVC 15.9.3 with /std:c++17)
    std::cout << v1 << "," << v2 << std::endl; // Expect "0,1", prints "0,0"

    // Prints the right values (or ought to with C++17 sequencing, anyway)
    std::cout << f() << "," << f() << std::endl; // Expect "2,3", prints "2,3"

    return 0;
}

The strange output (in x86 debug builds)

0,0
2,3

It looks like a compiler bug (so we filed the report): https://developercommunity.visualstudio.com/content/problem/347419/unexpected-return-from-lambda-with-static-local-va.html

In what way is the produced program wrong, such that it incorrectly prints 0 for both v1 and v2, but correctly prints 2, 3 after that? Any educated guess at what the compiler bug is?

As a workaround I used a capture instead:

auto f = [i = 0]() mutable {
    return i++;
};

UPDATE - as a side note, the output from the example above is different again in x86 release builds:

0,1
3,2

There is another existing issue with MSVC, where std::cout's << operator is not sequenced left-to-right despite /std:c++17 being set, which I would speculate results in 3,2 being output here, at least.

like image 997
Sam Twidale Avatar asked Dec 06 '18 22:12

Sam Twidale


1 Answers

MSVC compiles the following cleanly:

constexpr int foo() {
    static int i = 0;
    return i++;
}
static_assert(foo() == foo()); // oh no

That is not standards compliant.

So, what happens is that since C++17, lambdas are implicitly constexpr if they can be. MSVC wrongly decides that the lambda is constexpr, and so folds f() into a constant for v2 (that it got from v1). It doesn't do this when you directly output it because it apparently doesn't eagerly evaluate constexpr stuff like gcc does (or uses some other heuristic that we can't know about).

like image 187
Rakete1111 Avatar answered Sep 23 '22 22:09

Rakete1111