Zebrafish
Zebrafish

Reputation: 13876

How am I supposed to use SFINAE overload resolution here?

I guess any use of SFINAE could be considered a hack, but here I tried for a long time and the best I could manage was to use a default void* argument in one of the overloads:

struct Dog 
{
    Dog() {}
    void makeNull() {}
    
};

// If no .makeNull() function this is eliminated
template <typename T>
constexpr auto HasMakeNullFunction() -> decltype(std::declval<T>().makeNull(), bool())
{
    return true;
}
// And this one is called. But I could only manage to do it with a default void* p = nullptr
template <typename T>
constexpr bool HasMakeNullFunction(void* p = nullptr)
{
    return false;
}

int main()
{

    constexpr bool b = HasMakeNullFunction<Dog>(); // True
    constexpr bool b2 = HasMakeNullFunction<int>(); // False

}

What's the way you're supposed to do it? This does work, but the typical way to use SFINAE is with a specialized version that gets called when the substitution fails, right? Also, I don't like the use of the default void* as I could see a potential for a misuse and an implicit conversion to void*.

Upvotes: 1

Views: 187

Answers (2)

Patrick Roberts
Patrick Roberts

Reputation: 51876

Before C++20 concepts, it was typical to define a struct that conditionally derived from either std::true_type or std::false_type in <type_traits>:

#include <type_traits>

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

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

struct Dog 
{
    Dog() {}
    void makeNull() {}
};

int main()
{
    constexpr bool b = HasMakeNullFunction<Dog>::value; // true
    constexpr bool b2 = HasMakeNullFunction<int>::value; // false

    static_assert(b);
    static_assert(!b2);
}

godbolt.org

However, with concepts, it's even easier:

template <class T>
concept HasMakeNullFunction = requires (T v) {
    { v.makeNull() };
};

struct Dog 
{
    Dog() {}
    void makeNull() {}
};

int main()
{
    constexpr bool b = HasMakeNullFunction<Dog>; // true
    constexpr bool b2 = HasMakeNullFunction<int>; // false

    static_assert(b);
    static_assert(!b2);
}

godbolt.org

Upvotes: 2

songyuanyao
songyuanyao

Reputation: 172924

Your code doesn't work since when specifying Dog as template argument the calling to HasMakeNullFunction is ambiguous.

You can define a type trait to separate the two overloads completely. e.g.

template <typename T, typename = void>
struct has_makeNull : std::false_type {};
template <typename T>
struct has_makeNull<T, decltype(std::declval<T>().makeNull(), void())> : std::true_type {};

template <typename T>
constexpr auto HasMakeNullFunction() -> std::enable_if_t<has_makeNull<T>::value, bool>
{
    return true;
}
template <typename T>
constexpr auto HasMakeNullFunction() -> std::enable_if_t<!has_makeNull<T>::value, bool>
{
    return false;
}

LIVE

Upvotes: 2

Related Questions