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