10

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?

1 Answer 1

25

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).

Sign up to request clarification or add additional context in comments.

3 Comments

Will this paper get in to c++26 or will it be heading for the next revision?
@Gerdiner Still unclear.
@Gerdiner Alright now it's clear, heading for C++29.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.