Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);?

like image 335
Violet Giraffe Avatar asked Nov 04 '14 10:11

Violet Giraffe


2 Answers

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.

like image 128
Walter Avatar answered Nov 09 '22 17:11

Walter


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.

like image 37
Casey Avatar answered Nov 09 '22 17:11

Casey