From cbb87cf56437b9a9255203e82b3b74a2ad0a3160 Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Thu, 13 Aug 2026 16:57:23 +0200 Subject: [PATCH] Build a variant's alternative through access on load Loading declared the alternative (`head_type value;`), so a type whose default constructor is private, or absent, could not be an alternative at all, even with `boost::serialization::access` befriended. Build it the way a container element is built instead. Fixes #338. --- include/boost/serialization/variant.hpp | 13 ++++-- test/test_variant.cpp | 53 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/include/boost/serialization/variant.hpp b/include/boost/serialization/variant.hpp index 67ec4191d..03e98fe76 100644 --- a/include/boost/serialization/variant.hpp +++ b/include/boost/serialization/variant.hpp @@ -56,6 +56,7 @@ #include #include #include +#include // use visitor from boost::variant template @@ -159,11 +160,15 @@ struct variant_impl { // with an implementation that de-serialized to the address of the // aligned storage included in the variant. typedef typename mpl::front::type head_type; - head_type value; - ar >> BOOST_SERIALIZATION_NVP(value); - v = std::move(value);; + // The alternative is built the way a container element is, + // rather than declared, so that a type whose default + // constructor is private, or missing altogether, can be + // reached through boost::serialization::access. + detail::stack_construct value(ar, version); + ar >> boost::serialization::make_nvp("value", value.reference()); + v = std::move(value.reference()); head_type * new_address = & get(v); - ar.reset_object_address(new_address, & value); + ar.reset_object_address(new_address, & value.reference()); return; } typedef typename mpl::pop_front::type type; diff --git a/test/test_variant.cpp b/test/test_variant.cpp index 52535bf29..bbf8f3f7e 100644 --- a/test/test_variant.cpp +++ b/test/test_variant.cpp @@ -220,6 +220,57 @@ void test_reuse(const Variant & v){ std::remove(testfile); } +// An alternative whose default constructor is private, reachable only +// through boost::serialization::access. Loading a variant has to build its +// alternative the way a container element is built, rather than declare one, +// or such a type cannot be an alternative at all. Reported by +// KJTsanaktsidis in +// https://github.com/boostorg/serialization/issues/338. Thanks! +class PD { + friend class boost::serialization::access; + PD() : m_x(0) {} + int m_x; +public: + explicit PD(int x) : m_x(x) {} + int value() const { + return m_x; + } + template + void serialize(Archive & ar, const unsigned int /* version */){ + ar & boost::serialization::make_nvp("x", m_x); + } +}; + +int pd_value(const boost::variant & v){ + return boost::get(v).value(); +} + +#ifndef BOOST_NO_CXX17_HDR_VARIANT +int pd_value(const std::variant & v){ + return std::get(v).value(); +} +#endif + +template +void test_private_default_ctor(){ + const char * testfile = boost::archive::tmpnam(NULL); + BOOST_REQUIRE(testfile != NULL); + const Variant v(PD(42)); + { + test_ostream os(testfile, TEST_STREAM_FLAGS); + test_oarchive oa(os, TEST_ARCHIVE_FLAGS); + oa << boost::serialization::make_nvp("v", v); + } + Variant v1; + { + test_istream is(testfile, TEST_STREAM_FLAGS); + test_iarchive ia(is, TEST_ARCHIVE_FLAGS); + ia >> boost::serialization::make_nvp("v", v1); + } + BOOST_CHECK(42 == pd_value(v1)); + std::remove(testfile); +} + int test_boost_variant(){ std::cerr << "Testing boost_variant\n"; boost::variant v; @@ -229,6 +280,7 @@ int test_boost_variant(){ test_type(v1); v = 1; test_reuse(v); + test_private_default_ctor >(); return EXIT_SUCCESS; } @@ -260,6 +312,7 @@ int test_std_variant(){ test_type(v1); v = 1; test_reuse(v); + test_private_default_ctor >(); return EXIT_SUCCESS; } #endif