You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

361 lines
13 KiB

/*! \file helpers.hpp
\brief Internal helper functionality
\ingroup Internal */
/*
Copyright (c) 2014, Randolph Voorhies, Shane Grant
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of cereal nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CEREAL_DETAILS_HELPERS_HPP_
#define CEREAL_DETAILS_HELPERS_HPP_
#include <type_traits>
#include <cstdint>
#include <utility>
#include <memory>
#include <unordered_map>
#include <stdexcept>
#include <cereal/macros.hpp>
#include <cereal/details/static_object.hpp>
namespace cereal
{
// ######################################################################
//! An exception class thrown when things go wrong at runtime
/*! @ingroup Utility */
struct Exception : public std::runtime_error
{
explicit Exception( const std::string & what_ ) : std::runtime_error(what_) {}
explicit Exception( const char * what_ ) : std::runtime_error(what_) {}
};
// ######################################################################
//! The size type used by cereal
/*! To ensure compatability between 32, 64, etc bit machines, we need to use
a fixed size type instead of size_t, which may vary from machine to
machine. */
using size_type = uint64_t;
// forward decls
class BinaryOutputArchive;
class BinaryInputArchive;
// ######################################################################
namespace detail
{
struct NameValuePairCore {}; //!< Traits struct for NVPs
}
//! For holding name value pairs
/*! This pairs a name (some string) with some value such that an archive
can potentially take advantage of the pairing.
In serialization functions, NameValuePairs are usually created like so:
@code{.cpp}
struct MyStruct
{
int a, b, c, d, e;
template<class Archive>
void serialize(Archive & archive)
{
archive( CEREAL_NVP(a),
CEREAL_NVP(b),
CEREAL_NVP(c),
CEREAL_NVP(d),
CEREAL_NVP(e) );
}
};
@endcode
Alternatively, you can give you data members custom names like so:
@code{.cpp}
struct MyStruct
{
int a, b, my_embarrassing_variable_name, d, e;
template<class Archive>
void serialize(Archive & archive)
{
archive( CEREAL_NVP(a),
CEREAL_NVP(b),
cereal::make_nvp("var", my_embarrassing_variable_name) );
CEREAL_NVP(d),
CEREAL_NVP(e) );
}
};
@endcode
There is a slight amount of overhead to creating NameValuePairs, so there
is a third method which will elide the names when they are not used by
the Archive:
@code{.cpp}
struct MyStruct
{
int a, b;
template<class Archive>
void serialize(Archive & archive)
{
archive( cereal::make_nvp<Archive>(a),
cereal::make_nvp<Archive>(b) );
}
};
@endcode
This third method is generally only used when providing generic type
support. Users writing their own serialize functions will normally
explicitly control whether they want to use NVPs or not.
@internal */
template <class T>
class NameValuePair : detail::NameValuePairCore
{
private:
// If we get passed an array, keep the type as is, otherwise store
// a reference if we were passed an l value reference, else copy the value
using Type = typename std::conditional<std::is_array<typename std::remove_reference<T>::type>::value,
typename std::remove_cv<T>::type,
typename std::conditional<std::is_lvalue_reference<T>::value,
T,
typename std::decay<T>::type>::type>::type;
// prevent nested nvps
static_assert( !std::is_base_of<detail::NameValuePairCore, T>::value,
"Cannot pair a name to a NameValuePair" );
NameValuePair & operator=( NameValuePair const & ) = delete;
public:
//! Constructs a new NameValuePair
/*! @param n The name of the pair
@param v The value to pair. Ideally this should be an l-value reference so that
the value can be both loaded and saved to. If you pass an r-value reference,
the NameValuePair will store a copy of it instead of a reference. Thus you should
only pass r-values in cases where this makes sense, such as the result of some
size() call.
@internal */
NameValuePair( char const * n, T && v ) : name(n), value(std::forward<T>(v)) {}
char const * name;
Type value;
};
//! A specialization of make_nvp<> that simply forwards the value for binary archives
/*! @relates NameValuePair
@internal */
template<class Archive, class T> inline
typename
std::enable_if<std::is_same<Archive, ::cereal::BinaryInputArchive>::value ||
std::is_same<Archive, ::cereal::BinaryOutputArchive>::value,
T && >::type
make_nvp( const char *, T && value )
{
return std::forward<T>(value);
}
//! A specialization of make_nvp<> that actually creates an nvp for non-binary archives
/*! @relates NameValuePair
@internal */
template<class Archive, class T> inline
typename
std::enable_if<!std::is_same<Archive, ::cereal::BinaryInputArchive>::value &&
!std::is_same<Archive, ::cereal::BinaryOutputArchive>::value,
NameValuePair<T> >::type
make_nvp( const char * name, T && value)
{
return {name, std::forward<T>(value)};
}
//! Convenience for creating a templated NVP
/*! For use in inteneral generic typing functions which have an
Archive type declared
@internal */
#define CEREAL_NVP_(name, value) ::cereal::make_nvp<Archive>(name, value)
// ######################################################################
//! A wrapper around data that can be serialized in a binary fashion
/*! This class is used to demarcate data that can safely be serialized
as a binary chunk of data. Individual archives can then choose how
best represent this during serialization.
@internal */
template <class T>
struct BinaryData
{
//! Internally store the pointer as a void *, keeping const if created with
//! a const pointer
using PT = typename std::conditional<std::is_const<typename std::remove_pointer<T>::type>::value,
const void *,
void *>::type;
BinaryData( T && d, uint64_t s ) : data(std::forward<T>(d)), size(s) {}
PT data; //!< pointer to beginning of data
uint64_t size; //!< size in bytes
};
// ######################################################################
namespace detail
{
// base classes for type checking
/* The rtti virtual function only exists to enable an archive to
be used in a polymorphic fashion, if necessary. See the
archive adapters for an example of this */
class OutputArchiveBase { private: virtual void rtti(){} };
class InputArchiveBase { private: virtual void rtti(){} };
// forward decls for polymorphic support
template <class Archive, class T> struct polymorphic_serialization_support;
struct adl_tag;
// used during saving pointers
static const int32_t msb_32bit = 0x80000000;
static const int32_t msb2_32bit = 0x40000000;
}
// ######################################################################
//! A wrapper around size metadata
/*! This class provides a way for archives to have more flexibility over how
they choose to serialize size metadata for containers. For some archive
types, the size may be implicitly encoded in the output (e.g. JSON) and
not need an explicit entry. Specializing serialize or load/save for
your archive and SizeTags allows you to choose what happens.
@internal */
template <class T>
class SizeTag
{
private:
// Store a reference if passed an lvalue reference, otherwise
// make a copy of the data
using Type = typename std::conditional<std::is_lvalue_reference<T>::value,
T,
typename std::decay<T>::type>::type;
SizeTag & operator=( SizeTag const & ) = delete;
public:
SizeTag( T && sz ) : size(std::forward<T>(sz)) {}
Type size;
};
// ######################################################################
//! A wrapper around a key and value for serializing data into maps.
/*! This class just provides a grouping of keys and values into a struct for
human readable archives. For example, XML archives will use this wrapper
to write maps like so:
@code{.xml}
<mymap>
<item0>
<key>MyFirstKey</key>
<value>MyFirstValue</value>
</item0>
<item1>
<key>MySecondKey</key>
<value>MySecondValue</value>
</item1>
</mymap>
@endcode
\sa make_map_item
@internal */
template <class Key, class Value>
struct MapItem
{
using KeyType = typename std::conditional<
std::is_lvalue_reference<Key>::value,
Key,
typename std::decay<Key>::type>::type;
using ValueType = typename std::conditional<
std::is_lvalue_reference<Value>::value,
Value,
typename std::decay<Value>::type>::type;
//! Construct a MapItem from a key and a value
/*! @internal */
MapItem( Key && key_, Value && value_ ) : key(std::forward<Key>(key_)), value(std::forward<Value>(value_)) {}
MapItem & operator=( MapItem const & ) = delete;
KeyType key;
ValueType value;
//! Serialize the MapItem with the NVPs "key" and "value"
template <class Archive> inline
void CEREAL_SERIALIZE_FUNCTION_NAME(Archive & archive)
{
archive( make_nvp<Archive>("key", key),
make_nvp<Archive>("value", value) );
}
};
//! Create a MapItem so that human readable archives will group keys and values together
/*! @internal
@relates MapItem */
template <class KeyType, class ValueType> inline
MapItem<KeyType, ValueType> make_map_item(KeyType && key, ValueType && value)
{
return {std::forward<KeyType>(key), std::forward<ValueType>(value)};
}
namespace detail
{
//! Tag for Version, which due to its anonymous namespace, becomes a different
//! type in each translation unit
/*! This allows CEREAL_CLASS_VERSION to be safely called in a header file */
namespace{ struct version_binding_tag {}; }
// ######################################################################
//! Version information class
/*! This is the base case for classes that have not been explicitly
registered */
template <class T, class BindingTag = version_binding_tag> struct Version
{
static const std::uint32_t version = 0;
// we don't need to explicitly register these types since they
// always get a version number of 0
};
//! Holds all registered version information
struct Versions
{
std::unordered_map<std::size_t, std::uint32_t> mapping;
std::uint32_t find( std::size_t hash, std::uint32_t version )
{
const auto result = mapping.emplace( hash, version );
return result.first->second;
}
}; // struct Versions
} // namespace detail
} // namespace cereal
#endif // CEREAL_DETAILS_HELPERS_HPP_