How to use SFINAE to create 2 different implementations of the same method
In order to use std::enable_if<condition>
, you must be in a template over the condition. One option is to declare your function a template with default argument
template <typename T>
struct Serializer
{
template<bool pod = std::is_pod<T>::value> // template over condition
static typename std::enable_if<pod, CByteArray>::type
serialize(const T& value)
{ return CByteArray(); }
template<bool pod = std::is_pod<T>::value>
static typename std::enable_if<!pod, CByteArray>::type
serialize(const T& value)
{ return Serializer<HLVariant>::serialize(HLVariant(value)); }
};
template<>
struct Serializer<HLVariant>
{
static CByteArray serialize(const HLVariant&);
};
Alternatively, you can apply SFINAE directly at the scope of the class template:
template<typename T, typename = void> struct Serializer;
template<>
struct Serializer<HLVariant>
{
static CByteArray serialize(const HLVariant&)
{ return CByteArray(); }
};
template<typename T>
struct Serializer<T,typename std::enable_if<is_pod<T>::type>
{
static CByteArray serialize(const T&)
{ return CByteArray(); }
};
template<typename T>
struct Serializer<T,typename std::enable_if<!is_pod<T>::type>
{
static CByteArray serialize(const T&value)
{ return Serializer<HLVariant>::serialize(HLVariant(value));
};
Or you could get rid of the class Serializer and declare this directly via template functions:
inline CByteArray
serialize(const HLVariant&)
{ return CByteArray(); }
template<typename T>
inline typename enable_if<std::is_pod<T>::value, CByteArray>::type
serialize(const T&)
{ return CByteArray(); }
template<typename T>
inline typename enable_if<!std::is_pod<T>::value, CByteArray>::type
serialize(const T&value)
{ return serialize(HLVariant(value)); }
BTW, C++14 defines the very useful alias
template<bool C, typename T>
using enable_if_t = typename enable_if<C,T>::type;
but you can, of course, do that as well. This avoids the tedious typename
and ::type
all the time.
SFINAE is an acronym for "Substitution Failure Is Not An Error." By definition, that means it only applies when substituting template arguments for parameters in the definition of a template. Your serialize
functions are member functions of a class template, they are not themselves function templates. The direct answer would be to convert the functions into function templates (Live code):
template <typename> struct Serializer;
template <>
struct Serializer<HLVariant>
{
static CByteArray serialize(const HLVariant& /* value */)
{
return CByteArray();
}
};
template <typename T>
struct Serializer
{
template <typename U = T>
static typename std::enable_if<std::is_pod<U>::value, CByteArray>::type
serialize(const U& /* value*/)
{
static_assert(std::is_pod<U>::value, "Not a POD type");
return CByteArray();
}
template <typename U = T>
static typename std::enable_if<!std::is_pod<U>::value, CByteArray>::type
serialize(const U& value)
{
return Serializer<HLVariant>::serialize(HLVariant(value));
}
};
I've removed the redundant inline
s since all functions defined in a class body are implicitly inline, and I relocated the Serializer<HLVariant>
specialization to ensure that it is properly declared before being referenced. It's a bit silly to have a class with only static member functions; you could more reasonably implement this as a set of overloaded functions (Live code):
inline CByteArray serialize(const HLVariant& /* value */)
{
return CByteArray();
}
template <typename T>
inline typename std::enable_if<std::is_pod<T>::value, CByteArray>::type
serialize(const T& /* value*/)
{
static_assert(std::is_pod<T>::value, "Not a POD type");
return CByteArray();
}
template <typename T>
inline typename std::enable_if<!std::is_pod<T>::value, CByteArray>::type
serialize(const T& value)
{
return serialize(HLVariant(value));
}
int main()
{
int i = 0;
serialize(i);
serialize(CByteArray());
serialize(HLVariant());
}
Given that SFINAE use hampers code readability, I would prefer to use tag dispatching in this instance. Instead of managing overload resolution of two functions with SFINAE, have a third function that calls the appropriate implementation for POD or non-POD (Yet more live code):
inline CByteArray serialize(const HLVariant& /* value */)
{
return CByteArray();
}
template <typename T>
inline CByteArray serialize(std::true_type, const T& /* value*/)
{
static_assert(std::is_pod<T>::value, "Not a POD type");
return CByteArray();
}
template <typename T>
inline CByteArray serialize(std::false_type, const T& value)
{
return serialize(HLVariant(value));
}
template <typename T>
inline CByteArray serialize(const T& value)
{
return serialize(std::is_pod<T>{}, value);
}
SFINAE is powerful, but dangerous enough to be left safely locked away for problems that you can solve with simpler tools.