
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@resophonic.com> // 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@resophonic.com> // 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