3

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
Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
paladin324
  • 332
  • 1
  • 10

2 Answers2

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;
}
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • @NikosC. I would ask you to post this as an answer so I can mark the question as solved. – paladin324 Jun 11 '19 at 05:36
  • @paladin324 That wouldn't be an answer though. The question is "why doesn't clang accept it", not how to make it accept it :-) – Nikos C. Jun 11 '19 at 05:37
  • I am sorry, but I cannot accept that as a solution, as invoking a function-object in a constexpr context is what I am trying to do. I cannot rely on a runtime-check for other reasons. (I rely on the compile-time check to alternate the return type of f()). – paladin324 Jun 11 '19 at 05:38
  • @NikosC. - Everything I know about constant expressions tells me it's a compiler bug. – StoryTeller - Unslander Monica Jun 11 '19 at 05:39
  • 1
    @paladin324 - Sure, you can take by value and rely on a pair of compiler bugs that may very likely be fixed right under you. But I'd consider refactoring if I were you. And it's perfectly fine not to accept an answer, no need to apologize. – StoryTeller - Unslander Monica Jun 11 '19 at 05:41
  • @paladin324 Do not worry about it becoming a runtime check. The compiler will be able to eliminate one of the branches and there won't be a check. This has been an optimization long before `if constexpr` came along. Example: https://godbolt.org/z/VMf7rk There is no runtime branch. – Nikos C. Jun 11 '19 at 05:42
  • I am just not convinced that the bug is what you mentioned. The reason for that is that if constexpr requires its "argument" to always be evaluated at compile-time (it is not context-dependent). – paladin324 Jun 11 '19 at 05:44
  • 1
    @paladin324 - Exactly, that's the problem. The argument doesn't have to be a constant expression. The function has no knowledge if it's invoked "normally" or as part of evaluating a constant expression. That's exactly why it **may not** assume the argument is a constant expression. – StoryTeller - Unslander Monica Jun 11 '19 at 05:46
  • @NikosC. I am not worried about the runtime overhead. I want to employ this in a metaprogramming context. (Being able to use a constexpr lambda to provide a predicate, instead of relying on type-level implemented checks) . – paladin324 Jun 11 '19 at 05:48
3

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.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • Well, can't deny there is no lvalue-to-rvalue conversion here. I'm still stumped by the fact the lambda doesn't have to be `constexpr` though. It's calling a non-constexpr function in an `if constexpr`, wth? Edit: NVM: "satisfies the requirement of a constexpr function". – StoryTeller - Unslander Monica Jun 11 '19 at 09:59