From 860d9770591cb54eea72757b0a545c1468597e36 Mon Sep 17 00:00:00 2001 From: KJ Tsanaktsidis Date: Fri, 26 Dec 2025 16:19:15 -0800 Subject: [PATCH] Allow variant deserialization to make types with private constructors If a type has a private no-args constructor, but has friended boost::serialization::access, it can usually be deserialised as normal. However, it _can't_ be deserialized out of a variant, because the variant tries to invoke the default constructor. Fix this by constructing it through boost::serialization::access, which is designed to model "classes that can't be created with no arguments except through deserialization" --- include/boost/serialization/access.hpp | 4 +++ include/boost/serialization/std_variant.hpp | 20 +++++++---- include/boost/serialization/variant.hpp | 16 ++++++--- include/boost/serialization/variant2.hpp | 20 +++++++---- test/test_variant.cpp | 40 +++++++++++++++++---- 5 files changed, 78 insertions(+), 22 deletions(-) diff --git a/include/boost/serialization/access.hpp b/include/boost/serialization/access.hpp index f0c27a7e68..d63e02f003 100644 --- a/include/boost/serialization/access.hpp +++ b/include/boost/serialization/access.hpp @@ -129,6 +129,10 @@ class access { // class doesn't have a class-specific placement new defined. ::new(t)T; } + template + static T *construct_r(void * t) { + return ::new(t)T; + } template static T & cast_reference(U & u){ return static_cast(u); diff --git a/include/boost/serialization/std_variant.hpp b/include/boost/serialization/std_variant.hpp index c02309b48e..032c58c5e4 100644 --- a/include/boost/serialization/std_variant.hpp +++ b/include/boost/serialization/std_variant.hpp @@ -122,11 +122,19 @@ struct variant_impl // with an implementation that de-serialized to the address of the // aligned storage included in the variant. using type = mp::front; - type value; - ar >> BOOST_SERIALIZATION_NVP(value); - v = std::move(value); + struct ValueData { + alignas(type) unsigned char data[sizeof(type)]; + type *ptr_; + void * mem() { return static_cast(data); } + type * ptr() { return ptr_; } + ValueData() : ptr_(access::construct_r(mem())) {} + ~ValueData() { ptr()->~type(); } + } value; + + ar >> BOOST_SERIALIZATION_NVP(*value.ptr()); + v = std::move(*value.ptr()); type * new_address = & std::get(v); - ar.reset_object_address(new_address, & value); + ar.reset_object_address(new_address, value.ptr()); return; } //typedef typename mpl::pop_front::type type; @@ -149,7 +157,7 @@ struct variant_impl<0, Seq> template void load( - Archive & ar, + Archive & ar, std::variant& v, const unsigned int version ){ @@ -188,7 +196,7 @@ void serialize(Archive &, std::monostate &, const unsigned int /*version*/) namespace boost { namespace serialization { - + template struct tracking_level< std::variant diff --git a/include/boost/serialization/variant.hpp b/include/boost/serialization/variant.hpp index b888a59418..9d56db4d4a 100644 --- a/include/boost/serialization/variant.hpp +++ b/include/boost/serialization/variant.hpp @@ -159,11 +159,19 @@ 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);; + struct ValueData { + alignas(head_type) unsigned char data[sizeof(head_type)]; + head_type *ptr_; + void * mem() { return static_cast(data); } + head_type * ptr() { return ptr_; } + ValueData() : ptr_(access::construct_r(mem())) {} + ~ValueData() { ptr()->~head_type(); } + } value; + + ar >> BOOST_SERIALIZATION_NVP(*value.ptr()); + v = std::move(*value.ptr()); head_type * new_address = & get(v); - ar.reset_object_address(new_address, & value); + ar.reset_object_address(new_address, value.ptr()); return; } typedef typename mpl::pop_front::type type; diff --git a/include/boost/serialization/variant2.hpp b/include/boost/serialization/variant2.hpp index 7f8fcbde37..f5ef2770b0 100644 --- a/include/boost/serialization/variant2.hpp +++ b/include/boost/serialization/variant2.hpp @@ -92,11 +92,19 @@ struct variant_impl // with an implementation that de-serialized to the address of the // aligned storage included in the variant. using type = mp11::mp_front; - type value; - ar >> BOOST_SERIALIZATION_NVP(value); - v = std::move(value); + struct ValueData { + alignas(type) unsigned char data[sizeof(type)]; + type *ptr_; + void * mem() { return static_cast(data); } + type * ptr() { return ptr_; } + ValueData() : ptr_(access::construct_r(mem())) {} + ~ValueData() { ptr()->~type(); } + } value; + + ar >> BOOST_SERIALIZATION_NVP(*value.ptr()); + v = std::move(*value.ptr()); type * new_address = & variant2::get(v); - ar.reset_object_address(new_address, & value); + ar.reset_object_address(new_address, value.ptr()); return; } //typedef typename mpl::pop_front::type type; @@ -119,7 +127,7 @@ struct variant_impl template void load( - Archive & ar, + Archive & ar, variant2::variant & v, const unsigned int version ){ @@ -158,7 +166,7 @@ void serialize(Archive &ar, variant2::monostate &, const unsigned int /*version* namespace boost { namespace serialization { - + template struct tracking_level< variant2::variant diff --git a/test/test_variant.cpp b/test/test_variant.cpp index 3de1afd336..ab9a13e528 100644 --- a/test/test_variant.cpp +++ b/test/test_variant.cpp @@ -57,6 +57,32 @@ namespace boost { #include "A.hpp" #include "A.ipp" +class V { +private: + friend class boost::serialization::access; + int m_i; + V() : + m_i(0) + {} + template + void serialize(Archive& ar, unsigned /*version*/) + { + ar & BOOST_SERIALIZATION_NVP(m_i); + } +public: + V(int i) : + m_i(i) + {} + int get_i() const + { + return m_i; + } + bool operator==(const V& other) const + { + return m_i == other.m_i; + } +}; + class are_equal : public boost::static_visitor { @@ -156,6 +182,8 @@ void test(Variant & v) test_type(v); v = std::string("we can't stop here, this is Bat Country"); test_type(v); + v = V(67); + test_type(v); } #include @@ -166,10 +194,10 @@ void test(Variant & v) int test_boost_variant(){ std::cerr << "Testing boost_variant\n"; - boost::variant v; + boost::variant v; test(v); const A a; - boost::variant v1 = & a; + boost::variant v1 = & a; test_type(v1); return EXIT_SUCCESS; } @@ -180,10 +208,10 @@ int test_boost_variant(){ int test_boost_variant2(){ std::cerr << "Testing boost_variant2\n"; - boost::variant2::variant v; + boost::variant2::variant v; test(v); const A a; - boost::variant2::variant v1 = & a; + boost::variant2::variant v1 = & a; test_type(v1); return EXIT_SUCCESS; } @@ -194,10 +222,10 @@ int test_boost_variant2(){ #include int test_std_variant(){ std::cerr << "Testing Std Variant\n"; - std::variant v; + std::variant v; test(v); const A a; - std::variant v1 = & a; + std::variant v1 = & a; test_type(v1); return EXIT_SUCCESS; }