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:
helped a bit to get what works with #define ISSUE
commented out.
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);
};
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);
};