Search code examples
c++templatesc++17type-traitsclass-template

Member function template specialization (define function outside of the class definition)


I've been looking for solution of my issue over Stack Overflow and saw many similar topics, but there were no answers pointing to so specific case.

In the attached code, everything works unless I uncomment #define ISSUE. Then I'm getting many errors.

The goal here is to be able to only declare specializations in the class body. The implementation needs to be put in the same file after class definition.

Why this is not working? How to make it work?

My compiler supports C++17, nothing newer.

#include <cstdint>
#include <iostream>
#include <type_traits>

// #define ISSUE

template <typename T>
class Test
{
  public:
    template<typename U = T,
        std::enable_if_t<
            std::is_same<T,     bool>::value ||
            std::is_same<T,   int8_t>::value ||
            std::is_same<T,  uint8_t>::value ||
            (std::is_enum<T>::value && (sizeof(T) == 1U))
        ,int> = 0>
    static void special(T data);

#if defined(ISSUE)
    template<typename U = T,
        std::enable_if_t<
            std::is_same<T,  int32_t>::value ||
            std::is_same<T, uint32_t>::value ||
            (std::is_enum<T>::value && (sizeof(T) == 4U))
        ,int> = 0>
    static void special(T data);
#endif    
};

template <typename T>
template <typename, 
        std::enable_if_t<
            std::is_same<T,     bool>::value ||
            std::is_same<T,   int8_t>::value ||
            std::is_same<T,  uint8_t>::value ||
            (std::is_enum<T>::value && (sizeof(T) == 1U))
        ,int>>
void Test<T>::special(T data)
{
    std::cout << "print 8-bit\n";
}

#if defined(ISSUE)
template <typename T>
template <typename, 
        std::enable_if_t<
            std::is_same<T,  int32_t>::value ||
            std::is_same<T, uint32_t>::value ||
            (std::is_enum<T>::value && (sizeof(T) == 4U))
        ,int>>
void Test<T>::special(T data)
{
    std::cout << "print 32-bit\n";
}
#endif    

int main()
{
    Test<uint8_t>{}.special(5);
    Test<int8_t>{}.special(5);
    Test<bool>{}.special(true);
#if defined(ISSUE)
    Test<uint32_t>{}.special(5);
    Test<int32_t>{}.special(5);
#endif    
}

Only the post:

How to use std::enable_if on method of templated class with seperate declaration and definition via specialization

helped a bit to get what works with #define ISSUE commented out.


Solution

  • Why this is not working?

    Firstly, they are just template function overloads, not a specialization of member functions.

    The SFINAE can only work, if std::enable_if_t's condition depends up on the function's template parameter type. That means, in your shown code, it must depend upon U, rather than with class template parameter T.

    In addtion to that, the second parameter of std::enable_if_t is the return type of the function which will be SFINAEd. You have provided int as return, which must be void (or do not provide, since it is the default type for std::enable_if_t).


    How to make it work?

    I propose the alternative approach of using std::enable_if_t directly in the function signature as the return type, instead of inside a template parameter list.

    You can still keep the functions' definition separately outside the class as well.

    
    template <typename T>
    class Test
    {
    public:
        template<typename U = T>
        std::enable_if_t<
            std::is_same<U, bool>::value ||
            std::is_same<U, int8_t>::value ||
            std::is_same<U, uint8_t>::value ||
            (std::is_enum<U>::value && (sizeof(U) == 1U))
        > special(T data);
    
        template<typename U = T>
        std::enable_if_t<
            std::is_same<U, int32_t>::value ||
            std::is_same<U, uint32_t>::value ||
            (std::is_enum<U>::value && (sizeof(U) == 4U))
        > special(T data);
    };
    

    See live demo godbolt.org


    That being said, since you have access to C++17 compiler, have a look at if constexpr, which is a better alternative for the scenario. Or using fold expression you might also reduce the extended std::enable_if condition as simple as follows:

    // alias std::enable_if for type checking
    template <std::size_t N, typename U, typename... Ts>
    using enabled_types = std::enable_if_t<
        (std::is_same_v<U, Ts> || ...) ||
        (std::is_enum_v<U> && (sizeof(U) == N))
     >;
    
    template <typename T>
    class Test
    {
    public:
        template<typename U = T>
        enabled_types<1U, U, bool, int8_t, uint8_t> special(T data);
    
        template<typename U = T>
        enabled_types<4U, U, int32_t, uint32_t> special(T data);
    };
    

    See live demo godbolt.org