Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>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_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
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading