Consider the following code:
#include <concepts>
#include <functional>
struct A {
auto operator()(int const &) const noexcept {
return 0;
}
};
static_assert(std::invocable<A const &, int const &>);
struct X {
struct B {
auto operator()(int const &) const noexcept {
return 0;
}
};
static_assert(!std::invocable<B const &, int const &>); // Why?
};
static_assert(!std::invocable<X::B const &, int const &>); // Why?
void foo()
{
int x = 1;
A a;
X::B b;
std::invoke(a, x);
std::invoke(b, x);
}
On godbolt.org: https://godbolt.org/z/eT857zfrb
Results seem consistent between gcc, clang, and MSVC. Why does std::invocable fail on the sub-struct? The function foo seems to show that they are both "invocable".
>Solution :
At the point where you write static_assert(!std::invocable<B const &, int const &>);, within X, X is not yet a complete type. As cppreference states
If an instantiation of a template [in the
std::is_invocablefamily] depends, directly or indirectly, on an incomplete type, and that instantiation could yield a different result if that type were hypothetically completed, the behavior is undefined.
In this case, you are depending on the incomplete type X. (I’m sure the standard has a more detailed explanation.) std::invocable is defined in terms of a requires clause using std::invoke (at least, according to cppreference; GCC appears to use std::is_invocable directly), and std::invoke is defined to depend on std::is_invocable (though again, it appears this is not exactly the case in the implementations), so this static_assert is actually causing UB (hence the implementations aren’t wrong for their odd behavior.)
If you remove the static_assert within X, then the UB is removed and an assertion outside X will start having the correct behavior.
struct X {
struct B {
auto operator()(int const &) const noexcept {
return 0;
}
};
// UB!: static_assert(!std::invocable<B const &, int const &>);
};
static_assert(std::invocable<X::B const &, int const &>); // passes