Простое решение - проверьте, являются ли указатели функций взаимозависимыми
Это действительно очень просто сделать с помощью сопоставления с образцом.Мы можем написать функцию constexpr
, которую я назову checkInverse
, которая возвращает true, если типы инвертированы, и false в противном случае:
template<class S, class T>
constexpr bool checkInverse(S(*)(T), T(*)(S)) {
return true;
}
template<class S, class T, class Garbage>
constexpr bool checkInverse(S(*)(T), Garbage) {
return false;
}
Поскольку первый случай более специализирован, если онЕсли функция удовлетворена, то функция вернет true, а в противном случае вернет false.
Затем мы можем использовать это, чтобы проверить, совпадают ли методы Serialize
и Deserialize
класса друг с другом:
template<class T>
constexpr bool isValidPolicy() {
return checkInverse(T::Serialize, T::Deserialize);
}
Что если мы не уверены, что класс имеет Serialize
и Deserialize
метод?
Мы можем расширить isValidPolicy
, чтобы проверить это с помощью SFINAE.Теперь он вернет true, только если эти методы существуют, И они удовлетворяют взаимозависимости типа.
Если я позвоню isValidPolicy<Type>(0)
, он попытается использовать перегрузку int
.Если Serialize
и Deserialize
не существует, это приведет к перегрузке long
и вернет false.
template<class Policy>
constexpr auto isValidPolicy(int)
-> decltype(checkInverse(Policy::Serialize, Policy::Deserialize))
{
return checkInverse(Policy::Serialize, Policy::Deserialize);
}
template<class Policy>
constexpr auto isValidPolicy(long)
-> bool
{
return false;
}
Каковы минусы этого решения?
На первый взгляд это кажется хорошим решением, хотя у него есть несколько проблем.Если шаблон Serialize
и Deserialize
настроен, он не сможет выполнить преобразование в указатель на функцию.
Кроме того, будущие пользователи могут захотеть написать Deserialize
методы, которые возвращают объект, который может быть преобразован в сериализованный тип.Это может быть чрезвычайно полезно для непосредственного построения объекта в векторе без копирования, что повышает эффективность.Этот метод не позволяет писать так Deserialize
.
Расширенное решение - проверьте, существует ли Serialize
для определенного типа, и можно ли преобразовать в этот тип значение, возвращаемое Deserialize
Это решение является более общим и, в конечном счете, болееполезно.Он обеспечивает большую гибкость при написании Serialize
и Deserialize
, обеспечивая при этом определенные ограничения (а именно, что Deserialize(Serialize(T))
можно преобразовать в T
).
Проверка того, что выходной сигнал может быть преобразован в некоторый тип
Мы можем использовать SFINAE для проверки этого и обернуть его в функцию is_convertable_to
.
#include <utility>
#include <type_traits>
template<class First, class... T>
using First_t = First;
template<class Target, class Source>
constexpr auto is_convertable_to(Source const& source, int)
-> First_t<std::true_type, decltype(Target(source))>
{
return {};
}
template<class Target, class Source>
constexpr auto is_convertable_to(Source const& source, long)
-> std::false_type
{
return {};
}
Проверка, представляет ли тип действительный сериализатор
Мы можем использовать вышеуказанную проверку преобразования для этого.Это проверит его для данного типа, который должен быть передан в качестве параметра в шаблон.Результат дается в виде статической константы bool.
template<class Serializer, class Type>
struct IsValidSerializer {
using Serialize_t =
decltype(Serializer::Serialize(std::declval<Type>()));
using Deserialize_t =
decltype(Serializer::Deserialize(std::declval<Serialize_t>()));
constexpr static bool value = decltype(is_convertable_to<Type, Deserialize_t>(std::declval<Deserialize_t>(), 0))::value;
};
Пример ленивого десериализатора
Я упоминал ранее, что можно полагаться на наложение оператора преобразования для сериализации / десериализации.Это чрезвычайно мощный инструмент, который мы можем использовать для написания ленивых сериализаторов и десериализаторов.Например, если сериализованное представление представляет собой std::array
из char
, мы могли бы написать ленивый десериализатор следующим образом:
template<size_t N>
struct lazyDeserializer {
char const* _start;
template<class T>
operator T() const {
static_assert(std::is_trivially_copyable<T>(), "Bad T");
static_assert(sizeof(T) == N, "Bad size");
T value;
std::copy_n(_start, N, (char*)&value);
return value;
}
};
Как только мы получим это, написание политики Serialize
, которая работает с любымтривиально копируемый тип относительно прост:
#include <array>
#include <algorithm>
class SerializeTrivial {
public:
template<class T>
static std::array<char, sizeof(T)> Serialize(T const& value) {
std::array<char, sizeof(T)> arr;
std::copy_n((char const*)&value, sizeof(T), &arr[0]);
return arr;
}
template<size_t N>
static auto Deserialize(std::array<char, N> const& arr) {
return lazyDeserializer<N>{&arr[0]};
}
};