diff --git a/include/boost/serialization/optional.hpp b/include/boost/serialization/optional.hpp index ce1d364de..6887b9d3f 100644 --- a/include/boost/serialization/optional.hpp +++ b/include/boost/serialization/optional.hpp @@ -1,6 +1,7 @@ /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 // (C) Copyright 2002-4 Pavel Vozenilek . +// Copyright 2026 Gennaro Prota. // Distributed under 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) @@ -21,13 +22,16 @@ #endif #include +#include +#include #include #include #include #include #include -#include +#include #include +#include // function specializations must be defined in the appropriate // namespace - boost::serialization @@ -35,27 +39,91 @@ namespace boost { namespace serialization { namespace detail { +// The value of an optional is serialized like a single element of an +// STL container: when T is default constructible we serialize it in place, +// otherwise we route construction through save/load_construct_data so that +// types without a default constructor can be reconstructed on load. The +// default-constructible path is left untouched so that archives written by +// earlier versions of the library keep the same layout. + +// save the value: T is default constructible +template +typename boost::enable_if< + typename detail::is_default_constructible, + void +>::type +save_value(Archive & ar, const OT & ot){ + ar << boost::serialization::make_nvp("value", *ot); +} + +// save the value: T is not default constructible +template +typename boost::disable_if< + typename detail::is_default_constructible, + void +>::type +save_value(Archive & ar, const OT & ot){ + typedef typename OT::value_type value_type; + const value_type & v = *ot; + const boost::serialization::item_version_type item_version( + boost::serialization::version::value + ); + ar << BOOST_SERIALIZATION_NVP(item_version); + boost::serialization::save_construct_data_adl( + ar, + boost::addressof(v), + item_version + ); + ar << boost::serialization::make_nvp("value", v); +} + +// load the value: T is default constructible +template +typename boost::enable_if< + typename detail::is_default_constructible, + void +>::type +load_value(Archive & ar, OT & ot, const unsigned int version){ + if(0 == version){ + boost::serialization::item_version_type item_version(0); + boost::serialization::library_version_type library_version( + ar.get_library_version() + ); + if(boost::serialization::library_version_type(3) < library_version){ + ar >> BOOST_SERIALIZATION_NVP(item_version); + } + } + typename OT::value_type t; + ar >> boost::serialization::make_nvp("value", t); + ot = boost::move(t); +} + +// load the value: T is not default constructible +template +typename boost::disable_if< + typename detail::is_default_constructible, + void +>::type +load_value(Archive & ar, OT & ot, const unsigned int /* version */){ + typedef typename OT::value_type value_type; + boost::serialization::item_version_type item_version(0); + ar >> BOOST_SERIALIZATION_NVP(item_version); + detail::stack_construct aux(ar, item_version); + ar >> boost::serialization::make_nvp("value", aux.reference()); + ot = boost::move(aux.reference()); + ar.reset_object_address(boost::addressof(*ot), aux.address()); +} + // OT is of the form optional template void save_impl( Archive & ar, const OT & ot ){ - // It is an inherent limitation to the serialization of optional.hpp - // that the underlying type must be either a pointer or must have a - // default constructor. It's possible that this could change sometime - // in the future, but for now, one will have to work around it. This can - // be done by serialization the optional as optional - #ifndef BOOST_NO_CXX11_HDR_TYPE_TRAITS - BOOST_STATIC_ASSERT( - boost::serialization::detail::is_default_constructible::value - || boost::is_pointer::value - ); - #endif const bool tflag(ot); ar << boost::serialization::make_nvp("initialized", tflag); if (tflag){ - ar << boost::serialization::make_nvp("value", *ot); + save_value(ar, ot); } } @@ -72,19 +140,7 @@ void load_impl( ot.reset(); return; } - - if(0 == version){ - boost::serialization::item_version_type item_version(0); - boost::serialization::library_version_type library_version( - ar.get_library_version() - ); - if(boost::serialization::library_version_type(3) < library_version){ - ar >> BOOST_SERIALIZATION_NVP(item_version); - } - } - typename OT::value_type t; - ar >> boost::serialization::make_nvp("value",t); - ot = boost::move(t); + load_value(ar, ot, version); } } // detail diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 42b6cc3b3..17fcba316 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -146,6 +146,77 @@ int test_move_only(){ } #endif // move-only support available +// A type without a default constructor. It is reconstructed on load through +// save_construct_data / load_construct_data, which is exactly what an +// optional now relies on (see issue #121). m_i is the constructor +// argument, m_x is ordinary serialized state. +struct ND { + int m_i; + int m_x; + ND(int i, int x) : m_i(i), m_x(x) {} + explicit ND(int i) : m_i(i), m_x(0) {} + template + void serialize(Archive & ar, const unsigned int /* version */){ + ar & boost::serialization::make_nvp("x", m_x); + } + bool operator==(const ND & rhs) const { + return m_i == rhs.m_i && m_x == rhs.m_x; + } +}; + +namespace boost { +namespace serialization { + +template +void save_construct_data( + Archive & ar, const ND * p, const unsigned int /* version */ +){ + ar << boost::serialization::make_nvp("i", p->m_i); +} + +template +void load_construct_data( + Archive & ar, ND * p, const unsigned int /* version */ +){ + int i; + ar >> boost::serialization::make_nvp("i", i); + ::new(p) ND(i); +} + +} // serialization +} // boost + +template class Optional> +int test_non_default_ctor(){ + const char * testfile = boost::archive::tmpnam(NULL); + BOOST_REQUIRE(NULL != testfile); + + const Optional o_empty; + const Optional o_value(ND(7, 42)); + { + test_ostream os(testfile, TEST_STREAM_FLAGS); + test_oarchive oa(os, TEST_ARCHIVE_FLAGS); + oa << boost::serialization::make_nvp("o_empty", o_empty); + oa << boost::serialization::make_nvp("o_value", o_value); + } + // start each target in the opposite state, so load must both reset and + // reconstruct + Optional o_empty_a(ND(1, 1)); + Optional o_value_a; + { + test_istream is(testfile, TEST_STREAM_FLAGS); + test_iarchive ia(is, TEST_ARCHIVE_FLAGS); + ia >> boost::serialization::make_nvp("o_empty", o_empty_a); + ia >> boost::serialization::make_nvp("o_value", o_value_a); + } + BOOST_CHECK(! o_empty_a); + BOOST_CHECK(static_cast(o_value_a)); + BOOST_CHECK(o_value_a && *o_value == *o_value_a); + + std::remove(testfile); + return EXIT_SUCCESS; +} + #include #ifndef BOOST_NO_CXX17_HDR_OPTIONAL #include @@ -162,5 +233,9 @@ int test_main( int /* argc */, char* /* argv */[] ){ test_move_only(); #endif #endif + test_non_default_ctor(); + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + test_non_default_ctor(); + #endif return EXIT_SUCCESS; }