Boost logo

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