I've read some articles on SFINAE, but can't find a solution for my case. Here's what I want to do:
#include <type_traits>
struct CByteArray {};
struct HLVariant {
HLVariant() {}
HLVariant(const HLVariant&) {}
HLVariant(const CByteArray&) {}
};
template <typename T>
struct Serializer
{
static 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();
}
static inline typename std::enable_if<!std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
{
return Serializer<HLVariant>::serialize(HLVariant(value));
}
};
template <>
struct Serializer<HLVariant>
{
static inline CByteArray serialize(const HLVariant& value)
{
return CByteArray();
}
};
int main()
{
int i = 0;
Serializer<int>::serialize(i);
Serializer<CByteArray>::serialize(CByteArray());
Serializer<HLVariant>::serialize(HLVariant());
return 0;
}
But, of course, I'm getting error C2039: 'type' : is not a member of 'std::enable_if<false,CByteArray>'
How to achieve what I want?
Also, would it be possible to reorganize the Serializer
somehow, so that the template parameter could be deduced implicitly - Serializer::serialize(i);
instead of Serializer<int>::serialize(i);
?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With