Reputation: 342
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
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
Reputation: 170074
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