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".
CodePudding user response:
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_invocable
family] 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