From b29440c90adc6f7c5037efff642c2dc2eb4401f8 Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Wed, 22 Jul 2026 09:39:59 +0200 Subject: [PATCH] Add BOOST_CLASS_TEMPLATE_VERSION for versioning class templates `BOOST_CLASS_VERSION` writes a full specialization of the version trait, so it can only version a concrete class. A class template needs a partial specialization, which users previously had to write by hand and which neither the tutorial nor version.hpp explained. `BOOST_CLASS_TEMPLATE_VERSION` writes that partial specialization. Closes issue #179. --- doc/traits.html | 15 ++++ include/boost/serialization/version.hpp | 29 +++++++ test/Jamfile.v2 | 1 + test/test_class_template_version.cpp | 102 ++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 test/test_class_template_version.cpp diff --git a/doc/traits.html b/doc/traits.html index ddd1cbff1..b00765dc4 100644 --- a/doc/traits.html +++ b/doc/traits.html @@ -92,6 +92,21 @@

Version

BOOST_CLASS_VERSION(my_class, 2) which expands to the code above. +

+BOOST_CLASS_VERSION writes a full +specialization, so it applies to a concrete class. To assign a version to a +class template a partial specialization is needed instead, which +BOOST_CLASS_TEMPLATE_VERSION +provides. The template parameter list and the specialized type are each +passed parenthesized, as they normally contain commas: +


+BOOST_CLASS_TEMPLATE_VERSION((class T, class U), (my_class<T, U>), 2)
+
+This assigns version 2 to every instantiation of +my_class. Non-type template +parameters are written the same way, for example +(class T, std::size_t N) paired with +(my_class<T, N>).

Implementation Level

In the same manner as the above, the "level" of implementation of serialization is diff --git a/include/boost/serialization/version.hpp b/include/boost/serialization/version.hpp index f21e7f169..c71bb144c 100644 --- a/include/boost/serialization/version.hpp +++ b/include/boost/serialization/version.hpp @@ -72,6 +72,7 @@ const int version::value; #include #include +#include // specify the current version number for the class // version numbers limited to 8 bits !!! @@ -102,4 +103,32 @@ struct version \ } \ } +// Specify the current version number for a class template. Unlike +// BOOST_CLASS_VERSION, which writes a full specialization, this writes a +// partial one, so it works for a template rather than a concrete class. +// The template parameter list and the specialized type are each passed +// parenthesized (they usually contain commas): +// +// BOOST_CLASS_TEMPLATE_VERSION((class T, class U), (my_class), 2) +// +// version numbers limited to 8 bits !!! +#define BOOST_CLASS_TEMPLATE_VERSION(TEMPLATE_PARAMETERS, TYPE, N) \ +namespace boost { \ +namespace serialization { \ +template< BOOST_PP_REMOVE_PARENS(TEMPLATE_PARAMETERS) > \ +struct version< BOOST_PP_REMOVE_PARENS(TYPE) > \ +{ \ + typedef mpl::int_ type; \ + typedef mpl::integral_c_tag tag; \ + BOOST_STATIC_CONSTANT(int, value = version::type::value); \ + BOOST_MPL_ASSERT(( \ + boost::mpl::less< \ + boost::mpl::int_, \ + boost::mpl::int_<256> \ + > \ + )); \ +}; \ +} \ +} + #endif // BOOST_SERIALIZATION_VERSION_HPP diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 72716b945..8f0440184 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -64,6 +64,7 @@ test-suite "serialization" : [ test-bsl-run_files test_binary ] [ test-bsl-run_files test_class_info_save ] [ test-bsl-run_files test_class_info_load ] + [ test-bsl-run_files test_class_template_version ] [ test-bsl-run_files test_bitset ] [ test-bsl-run_files test_complex ] [ test-bsl-run_files test_contained_class : A ] diff --git a/test/test_class_template_version.cpp b/test/test_class_template_version.cpp new file mode 100644 index 000000000..0555c597c --- /dev/null +++ b/test/test_class_template_version.cpp @@ -0,0 +1,102 @@ +/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 +// test_class_template_version.cpp + +// Copyright 2026 Gennaro Prota +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// Tests BOOST_CLASS_TEMPLATE_VERSION, which assigns a serialization version +// to a class template through a partial specialization of the version trait. + +#include +#include +#include + +#include + +#if defined(BOOST_NO_STDC_NAMESPACE) +namespace std{ + using ::remove; +} +#endif + +#include +#include +#include +#include "test_tools.hpp" + +// a class template with two type parameters; serialize records the version +// it is given on load so the round trip can be checked +template +struct pair_holder { + T first; + U second; + unsigned int loaded_version; + pair_holder() : first(), second(), loaded_version(0) {} + pair_holder(T f, U s) : first(f), second(s), loaded_version(0) {} + template + void serialize(Archive & ar, const unsigned int version){ + loaded_version = version; + ar & boost::serialization::make_nvp("first", first); + ar & boost::serialization::make_nvp("second", second); + } +}; + +BOOST_CLASS_TEMPLATE_VERSION((class T, class U), (pair_holder), 5) + +// a class template mixing a type parameter and a non-type parameter +template +struct sized_holder { + T value; +}; + +BOOST_CLASS_TEMPLATE_VERSION((class T, std::size_t N), (sized_holder), 3) + +// a template we do not version, to confirm the default is still 0 +template +struct unversioned { + T value; +}; + +int test_main(int /* argc */, char * /* argv */[]){ + // the macro must set the compile time version for every instantiation of + // the template, regardless of the actual arguments + BOOST_STATIC_ASSERT( + (boost::serialization::version >::value == 5) + ); + BOOST_STATIC_ASSERT( + (boost::serialization::version >::value == 5) + ); + // works for a non-type parameter too + BOOST_STATIC_ASSERT( + (boost::serialization::version >::value == 3) + ); + // an unversioned template still defaults to 0 + BOOST_STATIC_ASSERT( + (boost::serialization::version >::value == 0) + ); + + const char * testfile = boost::archive::tmpnam(NULL); + BOOST_REQUIRE(NULL != testfile); + + const pair_holder saved(7, 2.5); + { + test_ostream os(testfile, TEST_STREAM_FLAGS); + test_oarchive oa(os, TEST_ARCHIVE_FLAGS); + oa << boost::serialization::make_nvp("ph", saved); + } + pair_holder loaded; + { + test_istream is(testfile, TEST_STREAM_FLAGS); + test_iarchive ia(is, TEST_ARCHIVE_FLAGS); + ia >> boost::serialization::make_nvp("ph", loaded); + } + BOOST_CHECK(loaded.first == saved.first); + BOOST_CHECK(loaded.second == saved.second); + // the version set by the macro must be delivered to serialize on load + BOOST_CHECK(loaded.loaded_version == 5); + + std::remove(testfile); + return EXIT_SUCCESS; +}