From 37f1928858a594d6db57126fbec233521fd3a99d Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Mon, 20 Jul 2026 09:39:33 +0200 Subject: [PATCH] Move, don't copy, the loaded value into the optional `load_impl` read the value into a local and copy-assigned it to the optional. A move-only type then failed to compile: `optional`'s perfect-forwarding assignment is removed by SFINAE when the value is not copy-assignable, leaving no viable `operator=`. So, move the local into the optional instead. The one-line fix was proposed by Venkat Murty in PR #330; this extends it with `boost::move` and a regression test. --- include/boost/serialization/optional.hpp | 3 +- test/test_optional.cpp | 60 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/include/boost/serialization/optional.hpp b/include/boost/serialization/optional.hpp index bc6d82a1e..ce1d364de 100644 --- a/include/boost/serialization/optional.hpp +++ b/include/boost/serialization/optional.hpp @@ -20,6 +20,7 @@ #include #endif +#include #include #include #include @@ -83,7 +84,7 @@ void load_impl( } typename OT::value_type t; ar >> boost::serialization::make_nvp("value",t); - ot = t; + ot = boost::move(t); } } // detail diff --git a/test/test_optional.cpp b/test/test_optional.cpp index e12605433..42b6cc3b3 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -2,6 +2,7 @@ // test_optional.cpp // (C) Copyright 2004 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) @@ -92,6 +93,59 @@ int test(){ return EXIT_SUCCESS; } +// A move-only value type: deleted copy, defaulted move. Loading an +// optional must move the deserialized value into place rather than copy +// it. +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) \ + && !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS) \ + && !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) +#define BOOST_SERIALIZATION_TEST_OPTIONAL_MOVE_ONLY + +struct M { + int m_x; + M() : m_x(0) {} + explicit M(int x) : m_x(x) {} + M(const M &) = delete; + M & operator=(const M &) = delete; + M(M &&) = default; + M & operator=(M &&) = default; + template + void serialize(Archive & ar, const unsigned int /* version */){ + ar & boost::serialization::make_nvp("x", m_x); + } +}; + +template class Optional> +int test_move_only(){ + const char * testfile = boost::archive::tmpnam(NULL); + BOOST_REQUIRE(NULL != testfile); + + Optional o_empty; + Optional o_value(M(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 + // assign + Optional o_empty_a(M(7)); + 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) && 42 == o_value_a->m_x); + + std::remove(testfile); + return EXIT_SUCCESS; +} +#endif // move-only support available + #include #ifndef BOOST_NO_CXX17_HDR_OPTIONAL #include @@ -102,5 +156,11 @@ int test_main( int /* argc */, char* /* argv */[] ){ #ifndef BOOST_NO_CXX17_HDR_OPTIONAL test(); #endif + #ifdef BOOST_SERIALIZATION_TEST_OPTIONAL_MOVE_ONLY + test_move_only(); + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + test_move_only(); + #endif + #endif return EXIT_SUCCESS; }