Как использовать SFINAE для создания 2 различных реализаций одного и того же метода

Я прочитал несколько статей о SFINAE, но не могу найти решение для моего случая. Вот что я хочу сделать:

#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;
}

но, конечно, я получаю error C2039: 'type' : is not a member of 'std::enable_if<false,CByteArray>'

как достичь того, чего я хочу?

кроме того, можно ли реорганизовать Serializer каким - то образом, чтобы параметр шаблона можно было вывести неявно -Serializer::serialize(i); вместо Serializer<int>::serialize(i);?

2 ответов


чтобы использовать std::enable_if<condition>, вы должны быть в шаблоне за состоянием. Один из вариантов-объявить вспомогательный шаблон функции-члена serialize_t

template <typename T>
struct Serializer
{
    template<bool is_pod>  // template over condition
    static typename std::enable_if< is_pod, CByteArray>::type serialize_t(const T& value)
    { return CByteArray(); }

    template<bool is_pod>
    static typename std::enable_if<!is_pod, CByteArray>::type serialize_t(const T& value)
    { return Serializer<HLVariant>::serialize(HLVariant(value)); }

    static CByteArray serialize(const T& value)
    { return serialize_t<std::is_pod<T>::value>(value); }
};

template<>
struct Serializer<HLVariant>
{
    static CByteArray serialize(const HLVariant&)
    { return CByteArray(); }
};

кроме того, вы можете применить SFINAE непосредственно в области шаблона класса:

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

или вы можете избавиться от сериализатора класса и объявить об этом непосредственно через функции шаблона:

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

кстати, C++14 определяет очень полезный псевдоним

template<bool C, typename T>
using enable_if_t = typename enable_if<C,T>::type;

но вы можете конечно, и это тоже. Это позволяет избежать утомительных typename и ::type все время.


SFINAE является аббревиатурой для " ошибка подстановки не является ошибкой."По определению это означает, что он применяется только при замене аргументов шаблона параметрами в определении шаблона. Ваш serialize функции являются функциями-членами шаблона класса, они сами по себе не являются шаблонами функций. Прямым ответом будет преобразование функций в шаблоны функций (текущий код):

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

Я удалил лишние inlines так как все функции, определенные в теле класса, неявно встроены, и я переместил Serializer<HLVariant> специализация, чтобы убедиться, что он правильно объявил перед указанием. Немного глупо иметь класс только со статическими функциями-членами; вы могли бы более разумно реализовать это как набор перегруженных функций (текущий код):

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());
}

учитывая, что использование SFINAE затрудняет читаемость кода, я бы предпочел использовать диспетчеризацию тегов в этом случае. Вместо управления перегрузкой разрешение двух функций с SFINAE, имеет третью функцию, которая вызывает соответствующую реализацию для POD или non-POD (еще более живой код):

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 является мощным, но достаточно опасным, чтобы быть надежно запертым для проблем, которые вы можете решить с помощью более простых инструментов.