Reputation: 33579
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);
?
Upvotes: 8
Views: 1937
Reputation: 45424
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.
Upvotes: 3
Reputation: 42554
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.
Upvotes: 3