I try to apply std::not_fn wrapper to an immediate consteval function.
But even the simplest C++20 example
#include <functional>
consteval bool f() { return false; }
// ok in GCC and Clang, error in MSVC
static_assert( std::not_fn(f)() );
works fine in GCC/Clang, but fails in Visual Studio with the error:
error C7596: 'f': cannot take address of immediate function outside of an immediate invocation
In C++26 we will get std::not_fn with the statically determined callable target. Currently it is implemented only in libc++ from Clang. But the example
static_assert( std::not_fn<f>()() );
fails with the following error in Clang:
error: no matching function for call to 'not_fn'
note: candidate template ignored: invalid explicitly-specified argument for template
Online demo: https://gcc.godbolt.org/z/dKofrdhb8
All errors are resolved if declare the function f as constexpr instead of consteval.
Shall std::not_fn (one or both overloads) be compatible with immediate functions?
The short version is
#include <functional>
consteval bool f() { return false; }
static_assert( std::not_fn(f)() ); // this is ok
static_assert( std::not_fn<f>()() ); // but this is ill-formed
auto x = std::not_fn<f>(); // ... because this is ill-formed
f is an immediate function. There are tight restrictions around how it can be used. Basically, it can be used in an immediate function context or as part of an expression that is a constant expression.
In particular, there are some things you cannot do with immediate functions. The result of a constant expression cannot include a pointer (or reference) to immediate function anywhere all the way down. Which means that std::not_fn<f>() is ill-formed because initializing the constant template parameter of std::not_fn with f would be a constant expression that has a "constituent value" that is an immediate function. That's just not allowed. And it's important to understand why this might be the case:
consteval bool is_even(int x) { return x % 2 == 0; }
template <auto F>
bool call_f(int x) { return F(x); }
int main(int argc, char**) {
return call_f<is_even>(argc);
}
If we were allowed to initialize the constant template argument with is_even, then what would prevent the call F(x)? F would just be a bool(*)(int) at that point right? But that would require persisting the function to runtime, which consteval is expressly there to avoid doing.
But this is fine:
static_assert( std::not_fn(f)() );
The use of f there makes this immediate-escalating which requires the whole expression to either be constant (which it is) or within an immediate function context (which it also is), so that works.
Note that Peter Dimov and I have a paper (P3603) which seeks to address this issue by properly formalizing the notion of consteval-only value, which would permit std::not_fn<f> to work by effectively being able to propagate through the "consteval-ness" of the value. Or, for my is_even example earlier, call_f<is_even> itself would be fine, but the call F(x) in the body would be ill-formed because it would have to be constant (and it is not).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With