Violet Giraffe
Violet Giraffe

Reputation: 33579

How to use SFINAE to create 2 different implementations of the same method

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

Answers (2)

Walter
Walter

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

Casey
Casey

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 inlines 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

Related Questions