|
Boost : |
From: troy d. straszheim (troy_at_[hidden])
Date: 2005-02-08 06:12:23
Robert Ramey wrote:
> The variant code in question complies with a) above but not b). As far as I
> know, variant isn't scheduled for inclusion in that standard so this not be
> a huge issue. On the other hand, I believe that the current variant
> exposes enough functionality to permit serialization without changes and
> without exposing any of its implementation. (I might be wrong here). If
> not, I believe only the smallest of additions to its public interface would
> be required.
OK, I got it. Files attached. No changes to variant, not even a
"friend", one header file (boost/serialization/variant.hpp) and an
improved test suite. I wish I had looked more closely earlier, now that
it is written I see what you were talking about: this is vastly more
elegant, about a third as much code, and all in one place. At the
moment it was just a rush to get something working, and there was code
there. Thanks Peter Dimov for your post with this code:
> apply_visitor( bind( variant_saver(), ref(ar), _1 ), v );
which worked practically right out of the box and got me rolling. You
know, the further you get into boost the cooler it gets.
troy d. straszheim
#ifndef BOOST_SERIALIZATION_VARIANT_HPP
#define BOOST_SERIALIZATION_VARIANT_HPP
//
// boost/seriaization/variant.hpp
// non-intrusive serialization of variant types
//
// copyright (c) 2005
// troy d. straszheim <troy_at_[hidden]>
// http://www.resophonic.com
//
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org for updates, documentation, and revision history.
//
// thanks to Robert Ramey and Peter Dimov.
//
#include <boost/serialization/split_free.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/greater_equal.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/throw_exception.hpp>
#include <boost/archive/archive_exception.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
namespace boost {
namespace serialization {
namespace variant {
namespace mpl = boost::mpl;
struct save_visitor : boost::static_visitor<>
{
template<class Archive, class T>
void operator()(Archive & ar, T const & value ) const
{
ar << BOOST_SERIALIZATION_NVP(value);
}
};
// variant_value_at returns this when we're reading from an
// archive and the "which" is at a position that is off the end
struct off_the_end_tag { };
// metafunction returns either the type at index i inside type
// sequence "types", or off_the_end_tag if i > size<types>.
// Needed to avoid instantating load_impl for variant::void_
template <int i, class types>
struct variant_value_at
{
typedef typename mpl::eval_if<
typename mpl::greater_equal<mpl::int_<i>, mpl::size<types> >::type
, mpl::identity<off_the_end_tag>
, mpl::at<types, mpl::int_<i> >
>::type type;
};
// This loads something of type Value from an archive, when
// Value is a "real" value, not variant::void_
template <typename Value>
struct load_impl {
template <class Archive, typename Variant>
inline static void load(Archive& ar, Variant& v)
{
Value value;
ar >> BOOST_SERIALIZATION_NVP(value);
v = value;
}
};
using boost::archive::archive_exception;
// This is executed if one tries to load a variant with a long
// type sequence into a variant with a shorter type sequence,
// e.g. variant<bool,int,short,long> -> variant<bool,int>. We
// need to catch this case because calls to this function are
// generated for all indices up to BOOST_VARIANT_LIMIT_TYPES,
// and we cannot create an object of type variant::void_, which
// we must in the primary load_impl template.
template<>
struct load_impl<off_the_end_tag> {
template <class Archive, typename Variant>
inline static void load(Archive& ar, Variant& v)
{
boost::throw_exception(archive_exception(archive_exception::stream_error));
}
};
} // namespace boost::serialization::variant
#define BOOST_VARIANT_LOADER_CASE_STATEMENT(Z, N, DATA) \
case N: { \
typedef typename \
variant::variant_value_at<N,typename boost::variant<BOOST_VARIANT_ENUM_PARAMS(T) \
>::types \
>::type value_t; \
variant::load_impl<value_t>::load(ar, v); \
} break;
// somewhere here we have to make the leap from a runtime int to a
// compile-time mpl::int_<> so we can dig the value type out of
// the variant. Is there a better idiom for this than
// preprocessor metaprogramming inside a switch statement? The
// switch logic is basically
// case 0: do_something_with<type_at_variant_index_0>(); break;
// case 1: do_something_with<type_at_variant_index_1>(); break;
template <class Archive, BOOST_VARIANT_ENUM_PARAMS(typename T) >
inline void load(Archive &ar,
boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& v,
const unsigned int /* file_version */)
{
int which;
ar >> BOOST_SERIALIZATION_NVP(which);
switch(which)
{
BOOST_PP_REPEAT(BOOST_VARIANT_LIMIT_TYPES,
BOOST_VARIANT_LOADER_CASE_STATEMENT, unused)
}
}
#undef BOOST_VARIANT_LOADER_CASE_STATEMENT
template <class Archive, BOOST_VARIANT_ENUM_PARAMS(typename T) >
inline void save(Archive &ar,
const boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& v,
const unsigned int /* file_version */)
{
int which = v.which();
ar << BOOST_SERIALIZATION_NVP(which);
// Suggested by Peter Dimov. Gorgeous, no?
apply_visitor( bind( variant::save_visitor(), boost::ref(ar), _1 ), v );
}
template <class Archive, BOOST_VARIANT_ENUM_PARAMS(typename T) >
inline void serialize(Archive &ar,
boost::variant< BOOST_VARIANT_ENUM_PARAMS(T) >& v,
const unsigned int file_version)
{
boost::serialization::split_free(ar, v, file_version);
}
} // namespace boost::serialization
} // namespace boost
#endif // BOOST_SERIALIZATION_VARIANT_HPP
//
// boost/seriaization/test_variant.cpp
// test of non-intrusive serialization of variant types
//
// copyright (c) 2005
// troy d. straszheim <troy_at_[hidden]>
// http://www.resophonic.com
//
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org for updates, documentation, and revision history.
//
// thanks to Robert Ramey and Peter Dimov.
//
#include <fstream>
#include <cstdio> // remove
#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
namespace std{
using ::remove;
}
#endif
#include "test_tools.hpp"
#include <boost/preprocessor/stringize.hpp>
#include BOOST_PP_STRINGIZE(BOOST_ARCHIVE_TEST)
#include <boost/serialization/nvp.hpp>
#include "throw_exception.hpp"
#include <boost/archive/archive_exception.hpp>
#include "A.hpp"
#include <boost/serialization/variant.hpp>
#include <boost/variant.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <iostream>
template <class T>
T archive_and_retrieve(const T& gets_written)
{
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(testfile != NULL);
{
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os);
oa << boost::serialization::make_nvp("written", gets_written);
}
T got_read;
{
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is);
ia >> boost::serialization::make_nvp("written", got_read);
}
std::remove(testfile);
return got_read;
}
template <class T>
void test_type(const T& in)
{
T out = archive_and_retrieve(in);
BOOST_CHECK_EQUAL(in, out);
}
//
// this verifies that if you try to read in a variant from a file
// whose "which" is illegal for the one in memory (that is, you're
// reading in to a different variant than you wrote out to) the load()
// operation will throw. One could concievably add checking for
// sequence length as well, but this would add size to the archive for
// dubious benefit.
//
void do_bad_read()
{
boost::variant<bool, float, int, std::string> big_variant;
big_variant = std::string("adrenochrome");
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(testfile != NULL);
{
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os);
oa << BOOST_SERIALIZATION_NVP(big_variant);
}
boost::variant<bool, float, int> little_variant;
{
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is);
bool exception_invoked = false;
BOOST_TRY {
ia >> BOOST_SERIALIZATION_NVP(little_variant);
} BOOST_CATCH (boost::archive::archive_exception e) {
BOOST_CHECK(boost::archive::archive_exception::stream_error == e.code);
exception_invoked = true;
}
BOOST_CHECK(exception_invoked);
}
}
int test_main( int /* argc */, char* /* argv */[] )
{
{
boost::variant<bool, int, float, double, A, std::string> v;
v = false;
test_type(v);
v = 1;
test_type(v);
v = (float) 2.3;
test_type(v);
v = (double) 6.4;
test_type(v);
v = std::string("we can't stop here, this is Bat Country");
test_type(v);
v = A();
test_type(v);
}
do_bad_read();
return boost::exit_success;
}
// EOF
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk