paladin324
paladin324

Reputation: 342

GCC and Clang disagree about constexpr-ness of lambda?

Why does Clang fail to compile the following code, with the message that the expression is not constexpr, and why does GCC not? Which compiler is correct? https://godbolt.org/z/nUhszh (Obviously, this is just an example. I do, in fact, need to be able to invoke a constexpr function-object in a constexpr context.)

#include <type_traits>

template <typename Predicate>
constexpr int f(Predicate&& pred) {
    if constexpr (pred(true)) {
        return 1;
    } 
    else {
        return 0;
    }
}

int main() {
    f([](auto m) {
        return std::is_same_v<decltype(m), bool>;
    });
}

Output of clang 8.0.0 with -std=c++17 -stdlib=libc++ -O1 -march=skylake:

<source>:5:19: error: constexpr if condition is not a constant expression

    if constexpr (pred(true)) {

                  ^

<source>:14:5: note: in instantiation of function template specialization 'f<(lambda at <source>:14:7)>' requested here

    f([](auto m) {

    ^

1 error generated.

Compiler returned: 1

Upvotes: 3

Views: 165

Answers (2)

Oliv
Oliv

Reputation: 18051

Clang is right. In the expression pred(true), the id-expression pred denotes a variable of reference type. Variable of reference type can appear in constant expression only if they are initialized by a constant expression or if their initialization is performed during the evaluation of the expression ([expr.const]/2.11).

So pred(true) is not a constant expression.

If you change the declaration of the parameter pred to Predicate pred, Pred will not be of reference type. The expression pred(true) will be equivalent to pred.operator()(true). pred will be an object-expression in a class member access expression, and as such, no lvalue-to-rvalue conversion will be applied to pred. So the id-expression pred will not have to be initialized by a constant expression (see [expr.const]/2.7.2) in order to be a constant expression. Then the function call is a constant expression because the call operator is implictly a constexpr function [expr.prim.lambda.closure]/4.

Those facts justify the proposal of @NikosC. to declare Pred as Predicate Pred. In this case your code will compile with both Clang and Gcc and will be standard compliant.

Upvotes: 3

By using the predicate in an if constexpr you expect it to be unconditionally a constant expression. But a constexpr function can always be called in a non-constexpr context with arguments that are not constant expressions. So a function parameter may never be assumed to be a constant expression.

GCC is therefore wrong to accept your code unmodified.

It just so happens that your specific example doesn't require an if constexpr to work in a constexpr context. A modified function:

template <typename Predicate>
constexpr int f(Predicate&& pred) {
    if (pred(true)) {
        return 1;
    } 
    else {
        return 0;
    }
}

Is invocable by both compilers when a constant expression is required:

int main() {
    constexpr int i = f([](auto m) constexpr {
        return std::is_same_v<decltype(m), bool>;
    });
    return i;
}

Upvotes: 3

Related Questions