super
super

Reputation: 288

Usage of lambda in constant expression

Take the following code:

template <typename T, typename U>
constexpr bool can_represent(U&& w) noexcept
{
    return [] (auto&& x) {
        try {
            return T(std::forward<U>(x)) == std::forward<U>(x);
        } catch(...) {
            return false;
        }
    } (std::forward<U>(w));
}

I am using this function in a constant expression (template).

gcc compiles it without a problem. clang and MSVC don't, lamenting that the function does not result in a constant expression.

Indeed, gcc did not immediately accept this either; it was getting hung up on the try, that normally wouldn't be allowed in a constexpr function. That's why I had to use an immediately invoked lambda expression. However, now it works, and considering it only works with gcc I'm quite confused.

Which compiler is correct? Is there a property of the lambda that permits this to work in a constexpr context, or is this some kind of non-standard gcc extension?

[I've used godbolt to compile with clang and MSVC, where as I have gcc 8.1.0 on my machine]

Upvotes: 1

Views: 1203

Answers (1)

JaMiT
JaMiT

Reputation: 17072

[gcc] was getting hung up on the try, that normally wouldn't be allowed in a constexpr function.

This is correct for a C++17 program. (C++20 relaxed this, so a try block can now be used in a constexpr function. However, it is only the try that is allowed; it is not allowed for execution to hit something that throws an exception.)

That's why I had to use an immediately invoked lambda expression.

The implication here is that your approach made your code valid. This is incorrect. Using an an immediately invoked lambda did not work around the problem; it swept the problem under the rug. The try is still a problem, but now compilers do not have to tell you it is a problem.

Using a lambda switches the constexpr criterion from the straight-forward "the function body must not contain a try-block" to the indirect "there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression". The tricky part here is that a violation of the latter criterion is "no diagnostic required", meaning that all the compilers are correct, whether or not they complain about this code. Hence my characterization of this as sweeping the problem under the rug.

So why is... that criterion is a long thing to repeat... what's the issue involving "core constant expressions"? C++17 removed the prohibition against lambdas in core constant expressions, so that much looks good. However, there is still a requirement that all function calls within the constexpr function also be themselves constexpr. Lambdas can become constexpr in two ways. First, they can be explicitly marked constexpr (but if you do that here, the complaint about the try block should come back). Second, they can simply satisfy the constexpr function requirements. However, your lambda contains a try, so it is not constexpr (in C++17).

Your lambda is not a valid constexpr function. Hence calling it is not allowed in a core constant expression. There is no execution path through can_represent() that avoids invoking your lambda. Therefore, can_represent is not a valid constexpr function, no diagnostic required.

Upvotes: 3

Related Questions