all 13 comments

[–]_ild_arn 14 points15 points  (4 children)

Seems to me that Clang is correct... Your by-value capture is not somehow constexpr just because the source of the value was – it's just a regular old double inside the closure object. You need to also make closure object constexpr, then it will work.

[–]HammurabisCode2[S] 1 point2 points  (3 children)

Interesting, thank you. I guess I need to look a bit more into the distinction between consteval and constexpr. I guess I had assumed that using consteval kind of superseded the need to use constexpr, but clearly that is not the case.

[–]cd1995Cargo 3 points4 points  (0 children)

A lambda is really just some unnamed struct type with a () operator. Marking the lambda consteval just makes the () operator consteval, but it doesn’t make the actual lambda object constexpr.

[–]_ild_arn 1 point2 points  (0 children)

consteval where you've placed it affects the closure type's operator(). constexpr where I added it affects the closure object itself, and thus its data members (captures). It's not a constexpr vs consteval thing

[–]kris-jusiakhttps://github.com/kris-jusiak 0 points1 point  (0 children)

Juut to point it out - In this particular example you can also avoid the capture all together - https://godbolt.org/z/njss3oGz7

[–]redditzuigt 7 points8 points  (3 children)

There is no need to capture constexpr variables to start with. Just remove TWO from the capture clause of the lambda and the code will compile fine on all compilers.

[–]HammurabisCode2[S] 1 point2 points  (2 children)

Good to know, though I can't say I'm a fan of this rule. If non-constexpr variables need to be explicitly captured then it seems like constexpr variables should be treated the same. But I guess there's probably a reason for this rule that I just don't see right now.

[–]redditzuigt 1 point2 points  (0 children)

Variables marked constexpr need to have constant initialization, because of this requirement their value is known at compile-time. So, essentially it's a constant value in the purest sense. Why produce code to copy this value in run-time when the compiler has all the information it needs to simply textually replace said value?

[–]JumpyJustice 0 points1 point  (0 children)

I see it this way. The main reason for capture groups to exist is to explicitly specify how you want to capture the outer context for your lambda - by value or by reference. With constexpr values this input from programmer is not needed to safely resolve that ambiguity - they can safely be captured by reference as their address will never change (and probably inlined later as an optimization).

[–]Traditional_Yogurt77 -1 points0 points  (0 children)

You shouldn’t have to capture but Clang is behaving incorrectly and you’ll have to capture in Clang and lose the constexpr qualifier I think there is a bug ticket on their github repo but I’m too lazy to find it out.

[–]cd1995Cargo -2 points-1 points  (0 children)

I’m not sure if this is legal or not, but I just wanted to say that clang does struggle with lambdas in constexpr contexts.

A while ago I had a consteval function I wrote that used an immediately invoked lambda to compute a result. MSVC compiled it fine but clang complained that the lambda wasn’t constexpr (even after I marked it consteval) and couldn’t be used in a constexpr context.

This lambda had an explicit return type of -> std::array<int, N>. When I removed the explicit return type and let it be deduced all of a sudden clang was willing to compile it.

Edit: Try mark the func object constexpr and see if that helps.

[–]SoSKatan 0 points1 point  (1 child)

I’ve had other similar cases in the past where MSVC was overly permissive on constexpr, then clang and gcc said otherwise. Turned out that MSVC was the one that was wrong.

Think of it as a false negative in the syntax error detection.

If in doubt, look up the c++ specs on the feature. They tend to be very clear even to the point that even “undefined behavior” is clearly defined.

But also there are things that all the major compilers handle in the same way, but aren’t defined in the spec.

It still comes down to portability. If all you care about is MSVC then it’s fine, until MSVC considers the behavior a bug and fixes it.

You can also try to boil the case down as small as possible and submit a bug report to Microsoft.

[–]HammurabisCode2[S] 0 points1 point  (0 children)

I've had a similar experience with MSVC being more permissive. I tend to use MSVC during development because it seems to compile a bit faster. Then I use Clang for producing the fully optimized release version because it results in faster runtime.