Home > Blockchain >  std::invocable seems to be acting differently on functors within structs
std::invocable seems to be acting differently on functors within structs

Time:07-21

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
  • Related