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 ); 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 } // namespace cereal
#endif // CEREAL_ACCESS_HPP_ #endif // CEREAL_ACCESS_HPP_

View File

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

View File

@@ -146,6 +146,63 @@ namespace cereal
has_non_member_serialize<T, InputArchive>(); 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> template <class T>
constexpr size_t sizeof_array( size_t rank = std::rank<T>::value ) constexpr size_t sizeof_array( size_t rank = std::rank<T>::value )

View File

@@ -43,9 +43,7 @@
class Base class Base
{ {
private:
public:
//private:
friend class cereal::access; friend class cereal::access;
template <class Archive> template <class Archive>
void serialize( Archive & ar ) void serialize( Archive & ar )
@@ -53,31 +51,37 @@ class Base
std::cout << "Base serialize" << std::endl; std::cout << "Base serialize" << std::endl;
ar( x ); ar( x );
} }
int x; int x;
}; };
class Derived : private Base class Derived : public Base
{ {
public: public:
//friend class cereal::access;
template <class Archive> template <class Archive>
void save( Archive & ar ) const void save( Archive & ar ) const
{ {
ar( cereal::base_class<Base>(this) );
std::cout << "Derived save" << std::endl; std::cout << "Derived save" << std::endl;
ar( y ); ar( y );
serialize( ar );
} }
template <class Archive> template <class Archive>
void load( Archive & ar ) void load( Archive & ar )
{ {
ar( cereal::base_class<Base>(this) );
std::cout << "Derived load" << std::endl; std::cout << "Derived load" << std::endl;
ar( y ); ar( y );
} }
int 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 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() int main()
{ {
std::cout << std::boolalpha << std::endl; 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; std::stringstream os;
cereal::BinaryOutputArchive archive(os); cereal::BinaryOutputArchive archive(os);
std::cout << has_size_method<B>::value << std::endl; Derived d;
std::cout << has_size_method<C>::value << std::endl; archive( d );
//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 );
//decltype(cereal::access::member_serialize(archive, d)); //decltype(cereal::access::member_serialize(archive, d));
//decltype(std::declval<Derived&>().serialize( std::declval<cereal::BinaryOutputArchive&>() )); //decltype(std::declval<Derived&>().serialize( std::declval<cereal::BinaryOutputArchive&>() ));
//d.serialize<cereal::BinaryOutputArchive>( archive ); //d.serialize<cereal::BinaryOutputArchive>( archive );