Added a way to disambiguate serialization in cases where cereal can't figure it out on its own thanks to C++. In

access.hpp, there is now a struct called specialize which can be specialized to explicitly specify what type of
serialization a class will need.  This is mostly useful for derived classes that switch from one method of serialization
(that is member/non-member serialize vs load/split).
This commit is contained in:
Shane Grant
2013-06-25 14:51:43 -07:00
parent 514b08a2a0
commit e1fe25b66f
4 changed files with 164 additions and 111 deletions

View File

@@ -124,6 +124,70 @@ namespace cereal
return T::load_and_allocate( ar );
}
};
//! A specifier used in conjunction with cereal::specialize to disambiguate
//! serialization in special cases
/*! @relates specialize */
enum class specialization
{
member_serialize,
member_load_save,
non_member_serialize,
non_member_load_save
};
//! A class used to disambiguate cases where cereal cannot detect a unique way of serializing a class
/*! cereal attempts to figure out which method of serialization (member vs. non-member serialize
or load/save pair) at compile time. If for some reason cereal cannot find a non-ambiguous way
of serializing a type, it will produce a static assertion complaining about this.
This can happen because you have both a serialize and load/save pair, or even because a base
class has a serialize (public or private with friend access) and a derived class does not
overwrite this due to choosing some other serialization type.
Specializing this class will tell cereal to explicitly use the serialization type you specify
and it will not complain about ambiguity in its compile time selection. However, if cereal detects
an ambiguity in specializations, it will continue to issue a static assertion.
@code{.cpp}
class MyParent
{
friend class cereal::access;
template <class Archive>
void serialize( Archive & ar ) {}
};
// Although serialize is private in MyParent, to cereal::access it will look public,
// even through MyDerived
class MyDerived : public MyParent
{
public:
template <class Archive>
void load( Archive & ar ) {}
template <class Archive>
void save( Archive & ar ) {}
};
// The save/load pair in MyDerived is ambiguous because serialize in MyParent can
// be accessed from cereal::access. This looks the same as making serialize public
// in MyParent, making it seem as though MyDerived has both a serialize and a load/save pair.
// cereal will complain about this at compile time unless we disambiguate:
namespace cereal
{
// This struct specialization will tell cereal which is the right way to serialize the ambiguity
template <class Archive> struct specialize<Archive, MyDerived, cereal::specialization::member_load_save> {};
// If we only had a disambiguation for a specific archive type, it would look something like this
template <> struct specialize<cereal::BinaryOutputArchive, MyDerived, cereal::specialization::member_load_save> {};
}
@endcode
@tparam T The type to specialize the serialization for
@tparam S The specialization type to use for T */
template <class Archive, class T, specialization S>
struct specialize : public std::false_type {};
} // namespace cereal
#endif // CEREAL_ACCESS_HPP_

View File

@@ -210,7 +210,8 @@ namespace cereal
//! Member serialization
template <class T> inline
typename std::enable_if<traits::is_output_serializable<T, ArchiveType>() && traits::has_member_serialize<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_member_serialize<T, ArchiveType>() ||
(traits::is_output_serializable<T, ArchiveType>() && traits::has_member_serialize<T, ArchiveType>()),
ArchiveType &>::type
operator & (T const & t)
{
@@ -220,7 +221,8 @@ namespace cereal
//! Non member serialization
template <class T> inline
typename std::enable_if<traits::is_output_serializable<T, ArchiveType>() && traits::has_non_member_serialize<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_non_member_serialize<T, ArchiveType>() ||
(traits::is_output_serializable<T, ArchiveType>() && traits::has_non_member_serialize<T, ArchiveType>()),
ArchiveType &>::type
operator & (T const & t)
{
@@ -230,7 +232,8 @@ namespace cereal
//! Member split (save)
template <class T> inline
typename std::enable_if<traits::is_output_serializable<T, ArchiveType>() && traits::has_member_save<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_member_load_save<T, ArchiveType>() ||
(traits::is_output_serializable<T, ArchiveType>() && traits::has_member_save<T, ArchiveType>()),
ArchiveType &>::type
operator & (T const & t)
{
@@ -240,7 +243,8 @@ namespace cereal
//! Non member split (save)
template <class T> inline
typename std::enable_if<traits::is_output_serializable<T, ArchiveType>() && traits::has_non_member_save<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_non_member_load_save<T, ArchiveType>() ||
(traits::is_output_serializable<T, ArchiveType>() && traits::has_non_member_save<T, ArchiveType>()),
ArchiveType &>::type
operator & (T const & t)
{
@@ -248,16 +252,7 @@ namespace cereal
return *self;
}
//! Non member split (save) non-const version
template <class T> inline
typename std::enable_if<traits::is_output_serializable<T, ArchiveType>() && traits::has_non_member_save<T, ArchiveType>(),
ArchiveType &>::type
operator & (T & t)
{
save(*self, t);
return *self;
}
//! Empty class specialization
template <class T> inline
typename std::enable_if<(Flags & AllowEmptyClassElision) &&
!traits::is_output_serializable<T, ArchiveType>() && traits::is_empty_class<T>(), ArchiveType &>::type
@@ -268,7 +263,7 @@ namespace cereal
//! No matching serialization
template <class T> inline
typename std::enable_if<!traits::is_output_serializable<T, ArchiveType>() &&
typename std::enable_if<!traits::is_specialized<T, ArchiveType>() && !traits::is_output_serializable<T, ArchiveType>() &&
(!(Flags & AllowEmptyClassElision) || ((Flags & AllowEmptyClassElision) && !traits::is_empty_class<T>())),
ArchiveType &>::type
operator & (T const & t)
@@ -365,7 +360,8 @@ namespace cereal
//! Member serialization
template <class T> inline
typename std::enable_if<traits::is_input_serializable<T, ArchiveType>() && traits::has_member_serialize<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_member_serialize<T, ArchiveType>() ||
(traits::is_input_serializable<T, ArchiveType>() && traits::has_member_serialize<T, ArchiveType>()),
ArchiveType &>::type
operator & (T && t)
{
@@ -375,7 +371,8 @@ namespace cereal
//! Non member serialization
template <class T> inline
typename std::enable_if<traits::is_input_serializable<T, ArchiveType>() && traits::has_non_member_serialize<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_non_member_serialize<T, ArchiveType>() ||
(traits::is_input_serializable<T, ArchiveType>() && traits::has_non_member_serialize<T, ArchiveType>()),
ArchiveType &>::type
operator & (T && t)
{
@@ -385,7 +382,8 @@ namespace cereal
//! Member split (load)
template <class T> inline
typename std::enable_if<traits::is_input_serializable<T, ArchiveType>() && traits::has_member_load<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_member_load_save<T, ArchiveType>() ||
(traits::is_input_serializable<T, ArchiveType>() && traits::has_member_load<T, ArchiveType>()),
ArchiveType &>::type
operator & (T && t)
{
@@ -395,7 +393,8 @@ namespace cereal
//! Non member split (load)
template <class T> inline
typename std::enable_if<traits::is_input_serializable<T, ArchiveType>() && traits::has_non_member_load<T, ArchiveType>(),
typename std::enable_if<traits::is_specialized_non_member_load_save<T, ArchiveType>() ||
(traits::is_input_serializable<T, ArchiveType>() && traits::has_non_member_load<T, ArchiveType>()),
ArchiveType &>::type
operator & (T && t)
{
@@ -403,6 +402,7 @@ namespace cereal
return *self;
}
//! Empty class specialization
template <class T> inline
typename std::enable_if<(Flags & AllowEmptyClassElision) &&
!traits::is_input_serializable<T, ArchiveType>() && traits::is_empty_class<T>(), ArchiveType &>::type
@@ -413,7 +413,7 @@ namespace cereal
//! No matching serialization
template <class T> inline
typename std::enable_if<!traits::is_input_serializable<T, ArchiveType>() &&
typename std::enable_if<!traits::is_specialized<T, ArchiveType>() && !traits::is_input_serializable<T, ArchiveType>() &&
(!(Flags & AllowEmptyClassElision) || ((Flags & AllowEmptyClassElision) && !traits::is_empty_class<T>())),
ArchiveType &>::type
operator & (T const & t)

View File

@@ -146,6 +146,63 @@ namespace cereal
has_non_member_serialize<T, InputArchive>();
}
// ######################################################################
namespace detail
{
template <class T, class A>
constexpr auto is_specialized_member_serialize() -> bool
{ return !std::is_base_of<std::false_type, specialize<A, T, specialization::member_serialize>>(); }
template <class T, class A>
constexpr auto is_specialized_member_load_save() -> bool
{ return !std::is_base_of<std::false_type, specialize<A, T, specialization::member_load_save>>(); }
template <class T, class A>
constexpr auto is_specialized_non_member_serialize() -> bool
{ return !std::is_base_of<std::false_type, specialize<A, T, specialization::non_member_serialize>>(); }
template <class T, class A>
constexpr auto is_specialized_non_member_load_save() -> bool
{ return !std::is_base_of<std::false_type, specialize<A, T, specialization::non_member_load_save>>(); }
// Considered an error if specialization exists for more than one type
template <class T, class A>
constexpr auto is_specialized_error() -> bool
{
return (is_specialized_member_serialize<T, A>() +
is_specialized_member_load_save<T, A>() +
is_specialized_non_member_serialize<T, A>() +
is_specialized_non_member_load_save<T, A>()) <= 1;
}
} // namespace detail
template <class T, class A>
constexpr auto is_specialized() -> bool
{
static_assert(detail::is_specialized_error<T, A>(), "More than one explicit specialization detected for type.");
return detail::is_specialized_member_serialize<T, A>() ||
detail::is_specialized_member_load_save<T, A>() ||
detail::is_specialized_non_member_serialize<T, A>() ||
detail::is_specialized_non_member_load_save<T, A>();
}
template <class T, class A>
constexpr auto is_specialized_member_serialize() -> bool
{ return is_specialized<T, A>() && detail::is_specialized_member_serialize<T, A>(); }
template <class T, class A>
constexpr auto is_specialized_member_load_save() -> bool
{ return is_specialized<T, A>() && detail::is_specialized_member_load_save<T, A>(); }
template <class T, class A>
constexpr auto is_specialized_non_member_serialize() -> bool
{ return is_specialized<T, A>() && detail::is_specialized_non_member_serialize<T, A>(); }
template <class T, class A>
constexpr auto is_specialized_non_member_load_save() -> bool
{ return is_specialized<T, A>() && detail::is_specialized_non_member_load_save<T, A>(); }
// ######################################################################
template <class T>
constexpr size_t sizeof_array( size_t rank = std::rank<T>::value )

View File

@@ -43,9 +43,7 @@
class Base
{
public:
//private:
private:
friend class cereal::access;
template <class Archive>
void serialize( Archive & ar )
@@ -53,31 +51,37 @@ class Base
std::cout << "Base serialize" << std::endl;
ar( x );
}
int x;
};
class Derived : private Base
class Derived : public Base
{
public:
//friend class cereal::access;
template <class Archive>
void save( Archive & ar ) const
{
ar( cereal::base_class<Base>(this) );
std::cout << "Derived save" << std::endl;
ar( y );
serialize( ar );
}
template <class Archive>
void load( Archive & ar )
{
ar( cereal::base_class<Base>(this) );
std::cout << "Derived load" << std::endl;
ar( y );
}
int y;
};
//ar( cereal::base_class<Test1>(this) );
namespace cereal
{
template <class Archive> struct specialize<Archive, Derived, cereal::specialization::member_load_save> {};
//template <class Archive> struct specialize<Archive, Derived, cereal::specialization::non_member_load_save> {};
}
// ###################################
struct Test1
@@ -249,88 +253,16 @@ namespace cereal
};
}
template<typename T>
struct has_size_method
{
private:
typedef std::true_type yes;
typedef std::false_type no;
template<typename U> static auto test(int) -> decltype(std::declval<U>().size(), yes());
template<typename> static no test(...);
public:
static constexpr bool value = std::is_same<decltype(test<T>(0)),yes>::value;
};
#include <vector>
template<class T, class A>
struct has_serialize_method
{
private:
typedef std::true_type yes;
typedef std::false_type no;
template<class T2, class A2> static auto test(int) -> decltype(std::declval<T2>().serialize(std::declval<A2&>()), yes());
//template<class T2, class A2> static auto test(int) -> decltype( access::member_serialize(std::declval<A2&>()std::declval<T2>().serialize(std::declval<A2&>()), yes());
//decltype( access::member_serialize(std::declval<A&>(), std::declval<T&>() ) )
template<typename, typename> static no test(...);
public:
static constexpr bool value = std::is_same<decltype(test<T, A>(0)),yes>::value;
};
struct B
{
int size();
};
struct C : private B
{
};
// ######################################################################
int main()
{
std::cout << std::boolalpha << std::endl;
//std::cout << "Base" << std::endl;
//std::cout << "ms " << cereal::traits::has_member_save<Base, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "nms " << cereal::traits::has_non_member_save<Base, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "ml " << cereal::traits::has_member_load<Base, cereal::BinaryInputArchive>() << std::endl;
//std::cout << "nml " << cereal::traits::has_non_member_load<Base, cereal::BinaryInputArchive>() << std::endl;
//std::cout << "mse " << cereal::traits::has_member_serialize<Base, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "nse " << cereal::traits::has_non_member_serialize<Base, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "iser " << cereal::traits::is_input_serializable<Base, cereal::BinaryInputArchive>() << std::endl;
//std::cout << "oser " << cereal::traits::is_output_serializable<Base, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << std::endl;
//std::cout << "Derived" << std::endl;
//std::cout << "ms " << cereal::traits::has_member_save<Derived, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "nms " << cereal::traits::has_non_member_save<Derived, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "ml " << cereal::traits::has_member_load<Derived, cereal::BinaryInputArchive>() << std::endl;
//std::cout << "nml " << cereal::traits::has_non_member_load<Derived, cereal::BinaryInputArchive>() << std::endl;
//std::cout << "mse " << cereal::traits::has_member_serialize<Derived, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "nse " << cereal::traits::has_non_member_serialize<Derived, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << "iser " << cereal::traits::is_input_serializable<Derived, cereal::BinaryInputArchive>() << std::endl;
//std::cout << "oser " << cereal::traits::is_output_serializable<Derived, cereal::BinaryOutputArchive>() << std::endl;
//std::cout << std::endl;
std::stringstream os;
cereal::BinaryOutputArchive archive(os);
std::cout << has_size_method<B>::value << std::endl;
std::cout << has_size_method<C>::value << std::endl;
//std::cout << has_serialize_method<int, cereal::BinaryInputArchive>::value << std::endl;
//std::cout << has_serialize_method<Base, cereal::BinaryInputArchive>::value << std::endl;
//std::cout << has_serialize_method<Derived, cereal::BinaryInputArchive>::value << std::endl;
//Derived d;
//d.serialize( archive );
Derived d;
archive( d );
//decltype(cereal::access::member_serialize(archive, d));
//decltype(std::declval<Derived&>().serialize( std::declval<cereal::BinaryOutputArchive&>() ));
//d.serialize<cereal::BinaryOutputArchive>( archive );