Compare commits

..

2 Commits

Author SHA1 Message Date
Gennaro Prota 37f1928858 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.
2026-07-20 09:56:46 +02:00
Gennaro Prota 22ae0cd043 Run CI on merge-queue candidates 2026-07-17 18:01:05 +02:00
6 changed files with 68 additions and 52 deletions
+3 -18
View File
@@ -7,6 +7,7 @@ on:
- master
- develop
- feature/**
merge_group:
env:
UBSAN_OPTIONS: print_stacktrace=1
@@ -243,15 +244,7 @@ jobs:
run: |
cd ../boost-root
export ADDRMD=${{matrix.address-model}}
./b2 -j3 libs/$LIBRARY/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release
- name: Run examples
run: |
cd ../boost-root
export ADDRMD=${{matrix.address-model}}
# The demos use fixed temp filenames, so run them serially (-j1)
# to avoid concurrent runs clobbering each other's archive files.
./b2 -j1 libs/$LIBRARY/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release
./b2 -j3 libs/$LIBRARY/test libs/$LIBRARY/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release
windows:
strategy:
@@ -306,15 +299,7 @@ jobs:
shell: cmd
run: |
cd ../boost-root
b2 -j3 libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
- name: Run examples
shell: cmd
run: |
cd ../boost-root
rem The demos use fixed temp filenames, so run them serially (-j1)
rem to avoid concurrent runs clobbering each other's archive files.
b2 -j1 libs/%LIBRARY%/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
b2 -j3 libs/%LIBRARY%/test libs/%LIBRARY%/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
posix-cmake-subdir:
strategy:
-2
View File
@@ -11,8 +11,6 @@ project libs/serialization/example
: requirements <library>../build//boost_serialization
;
import testing ;
import ../util/test :
run-template
run-invoke
+1 -5
View File
@@ -18,12 +18,8 @@
#include <boost/config.hpp>
// GCC's <filesystem> before version 9 is the initial, incomplete
// implementation (shipped in a separate libstdc++fs and buggy at runtime),
// so the feature is disabled there.
#if defined(__has_include)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L) \
&& ! (defined(__GNUC__) && ! defined(__clang__) && __GNUC__ < 9)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L)
# define BOOST_SERIALIZATION_HAS_STD_FILESYSTEM
# endif
#endif
+2 -1
View File
@@ -20,6 +20,7 @@
#include <optional>
#endif
#include <boost/move/utility_core.hpp>
#include <boost/serialization/item_version_type.hpp>
#include <boost/serialization/library_version_type.hpp>
#include <boost/serialization/version.hpp>
@@ -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
+2 -26
View File
@@ -17,11 +17,8 @@
// `std::filesystem::path` is a C++17 feature; when it isn't available, this
// test has nothing to exercise and simply succeeds.
// Note the guard for GCC < 9, which matches the one in
// boost/serialization/filesystem.hpp: see the comment there for the why.
#if defined(__has_include)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L) \
&& ! (defined(__GNUC__) && ! defined(__clang__) && __GNUC__ < 9)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L)
# define BOOST_SERIALIZATION_TEST_STD_FILESYSTEM
# endif
#endif
@@ -32,11 +29,6 @@
#include <boost/serialization/filesystem.hpp>
#include <filesystem>
#include <type_traits>
#ifndef BOOST_NO_STD_WSTREAMBUF
#include <boost/archive/text_woarchive.hpp>
#endif
void check_roundtrip(const std::filesystem::path & original){
const char * testfile = boost::archive::tmpnam(NULL);
@@ -65,21 +57,6 @@ std::filesystem::path from_utf8(const std::string & utf8){
#endif
}
// The wide *text* archive is the one archive that cannot round-trip non-ASCII
// content: it transcodes the narrow UTF-8 std::string through the stream
// locale's codecvt (codecvt_null), which mangles bytes outside the ASCII
// range. Every other archive in the test list preserves them -- narrow text
// and XML store the bytes verbatim, binary stores them raw, and wide XML
// installs a UTF-8 codecvt. So the non-ASCII case is exercised everywhere
// except that single archive.
bool archive_round_trips_non_ascii(){
#ifndef BOOST_NO_STD_WSTREAMBUF
return ! std::is_same<test_oarchive, boost::archive::text_woarchive>::value;
#else
return true;
#endif
}
int test_main(int /* argc */, char * /* argv */ []){
check_roundtrip(std::filesystem::path()); // empty
check_roundtrip("foo/bar/baz.txt"); // relative
@@ -89,8 +66,7 @@ int test_main(int /* argc */, char * /* argv */ []){
// The escapes are the UTF-8 encoding of U+00E9, U+00EF and U+00FC
// (e-acute, i-diaeresis, u-diaeresis); written as `\x` so the source
// file stays pure ASCII and encoding-independent.
if(archive_round_trips_non_ascii())
check_roundtrip(from_utf8("caf\xC3\xA9/na\xC3\xAF" "ve/\xC3\xBC.txt"));
check_roundtrip(from_utf8("caf\xC3\xA9/na\xC3\xAF" "ve/\xC3\xBC.txt"));
return EXIT_SUCCESS;
}
+60
View File
@@ -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<M> 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<class Archive>
void serialize(Archive & ar, const unsigned int /* version */){
ar & boost::serialization::make_nvp("x", m_x);
}
};
template<template<class> class Optional>
int test_move_only(){
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);
Optional<M> o_empty;
Optional<M> 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<M> o_empty_a(M(7));
Optional<M> 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<bool>(o_value_a) && 42 == o_value_a->m_x);
std::remove(testfile);
return EXIT_SUCCESS;
}
#endif // move-only support available
#include <boost/serialization/optional.hpp>
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
#include <optional>
@@ -102,5 +156,11 @@ int test_main( int /* argc */, char* /* argv */[] ){
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
test<std::optional>();
#endif
#ifdef BOOST_SERIALIZATION_TEST_OPTIONAL_MOVE_ONLY
test_move_only<boost::optional>();
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
test_move_only<std::optional>();
#endif
#endif
return EXIT_SUCCESS;
}