Search code examples
c++templatessfinae

SFINAE for types that don't have particular members


Why doesn't the following code compile and what is the most concise solution for enabling templates for types with particular members? Also it compiles if template variable is replaced with an expression, which is used to initialize it, directly.

#include <iostream>

template<class T>
constexpr bool is_rect = std::is_same_v<decltype(T::left, T::top, T::right, T::bottom, void()), void>;

// compiles if is_rect<T> is replaced with the expression directly, but of course it's not a solution
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
    std::cout << "rect enabled\n";
}

template<class T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
void f(T)
{
    std::cout << "arithmetic enabled\n";
}

struct ActualType { float left, top, right, bottom; };

int main()
{   
    f(ActualType{});
    f(int{});
    return 0;
}

Solution

  • The problem is that is_rect<T> is always specified as template argument for std::enable_if_t as part of the signature of f(), and it's invalid expression when T doesn't have particular members.

    You can apply partial specialization to make is_rect gives true or false based on the type has particular members or not. e.g.

    template<class T, class = void>
    constexpr bool is_rect = false;
    template<class T>
    constexpr bool is_rect<T, std::void_t<decltype(T::left, T::top, T::right, T::bottom)>> = true;
    

    then

    template<class T, std::enable_if_t<is_rect<T>, int> = 0>
    void f(T)
    {
        std::cout << "rect enabled\n";
    }
    

    LIVE