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 @@
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>).
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;
+}