Search code examples
c++c++17sfinaerequires-expression

Check if member function is defined using SFINAE instead of requires clause


I want to check if a specific member function of a class is defined and simultaneously not disabled by SFINAE. The following code works:

struct A {
    int foo() { return 3; }
};

struct B {};

template <class, class = void>
struct hasFoo : std::false_type {};

template <class T>
struct hasFoo<T, std::void_t<decltype(std::declval<T>().foo())>>
    : std::true_type {};

template <class T>
    struct hasFoo20 : std::bool_constant < requires(T t) {
    t.foo();
} > {};

static_assert(hasFoo<A>::value);
static_assert(!hasFoo<B>::value);
static_assert(hasFoo20<A>::value);
static_assert(!hasFoo20<B>::value);

Link

but if I have a member function in A, which itself is enabled or disabled using SFINAE, I cannot get it the following working:

template <typename T = int>
struct A {
    template <typename = void, std::enable_if_t<std::is_same_v<T, int>, bool> = true>
    T foo() {
        return 3;
    }
};

template <class, class = void>
struct hasFoo : std::false_type {};

template <class T>
struct hasFoo<T, std::void_t<decltype(std::declval<T>().foo())>>
    : std::true_type {};

template <class T>
    struct hasFoo20 : std::bool_constant < requires(T t) {
    t.foo();
} > {};

static_assert(hasFoo<A<>>::value);
static_assert(!hasFoo<A<double>>::value);

static_assert(hasFoo20<A<>>::value);
static_assert(!hasFoo20<A<double>>::value);

Link For the second case, only the C++20 code works as I want it, but I have to make it C++17 compatible.

The SFINAE code fails with error: no type named 'type' in 'struct std::enable_if<false, bool>', which is expected since the enable_if_t of foo is evaluated.

Can someone give me a hint? Thanks!


Solution

  • The first step of substitution failure is not an error (SFINAE) is substitution. If a template doesn't substitute anything and causes an error, it's just a hard error. Therefore, SFINAE must be dependent on the template parameters it appears in. Even though T looks like a template parameter of foo, it's not, it's from the enclosing class.

    One solution is

    template <typename U = T, std::enable_if_t<std::is_same_v<U, int>, bool> = false>
    T foo() {
        return 3;
    }