diff --git a/include/boost/archive/detail/iserializer.hpp b/include/boost/archive/detail/iserializer.hpp index 59c3ba615..6a35b9992 100644 --- a/include/boost/archive/detail/iserializer.hpp +++ b/include/boost/archive/detail/iserializer.hpp @@ -18,6 +18,7 @@ // iserializer.hpp: interface for serialization system. // (C) Copyright 2002 Robert Ramey - http://www.rrsd.com . +// 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) @@ -35,6 +36,7 @@ namespace std{ } // namespace std #endif +#include #include #include @@ -46,10 +48,12 @@ namespace std{ #ifndef BOOST_SERIALIZATION_DEFAULT_TYPE_INFO #include #endif +#include #include #include #include +#include #include #include #include @@ -560,10 +564,32 @@ template struct load_enum_type { template static void invoke(Archive &ar, T &t){ - // convert integers to correct enum to load - int i; - ar >> boost::serialization::make_nvp(NULL, i); - t = static_cast< T >(i); + // Enumerators were always stored as int before archive library version + // 21; read that layout from any older archive so existing data keeps + // loading. From version 21 on, an enumerator is stored as an int if it + // fits in an int; otherwise as the enum's underlying type (see + // save_enum_type). + if(ar.get_library_version() + < boost::serialization::library_version_type(21) + ){ + int i; + ar >> boost::serialization::make_nvp(NULL, i); + t = static_cast< T >(i); + return; + } + #ifndef BOOST_NO_UNDERLYING_TYPE + typedef typename boost::underlying_type< T >::type underlying_type; + typedef typename boost::conditional< + sizeof(underlying_type) <= sizeof(int), + int, + underlying_type + >::type load_type; + #else + typedef int load_type; + #endif + load_type lt; + ar >> boost::serialization::make_nvp(NULL, lt); + t = static_cast< T >(lt); } }; diff --git a/include/boost/archive/detail/oserializer.hpp b/include/boost/archive/detail/oserializer.hpp index 1a9bddec8..e0d568e8c 100644 --- a/include/boost/archive/detail/oserializer.hpp +++ b/include/boost/archive/detail/oserializer.hpp @@ -18,6 +18,7 @@ // oserializer.hpp: interface for serialization system. // (C) Copyright 2002 Robert Ramey - http://www.rrsd.com . +// 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) @@ -29,6 +30,8 @@ #include +#include + #include #include @@ -46,6 +49,7 @@ #include #include +#include #include #include #include @@ -488,9 +492,25 @@ struct save_enum_type { template static void invoke(Archive &ar, const T &t){ - // convert enum to integers on save - const int i = static_cast(t); - ar << boost::serialization::make_nvp(NULL, i); + // Save an enumerator as an integer wide enough to hold every value of + // the enum's underlying type. An underlying type no wider than int + // keeps the historical layout (a plain int), so existing archives are + // unaffected; a wider underlying type is written at full width instead + // of being truncated to int. Archives that need the wider layout carry + // library version 21 or greater (see load_enum_type). + #ifndef BOOST_NO_UNDERLYING_TYPE + typedef typename boost::underlying_type< T >::type underlying_type; + typedef typename boost::conditional< + sizeof(underlying_type) <= sizeof(int), + int, + underlying_type + >::type save_type; + #else + // no way to query the underlying type: keep the historical int + typedef int save_type; + #endif + const save_type st = static_cast< save_type >(t); + ar << boost::serialization::make_nvp(NULL, st); } }; diff --git a/src/basic_archive.cpp b/src/basic_archive.cpp index 313728ecd..f2e137f54 100644 --- a/src/basic_archive.cpp +++ b/src/basic_archive.cpp @@ -85,9 +85,11 @@ BOOST_ARCHIVE_SIGNATURE(){ // was fully constructed. // 19- Boost 1.76 April 2021 // 20- Boost 1.84 April 2021 +// 21- enumerators are stored as int (if they fit) or as the enum's underlying +// type, instead of always as int BOOST_SYMBOL_VISIBLE boost::serialization::library_version_type BOOST_ARCHIVE_VERSION(){ - return boost::serialization::library_version_type(20); + return boost::serialization::library_version_type(21); } } // namespace archive diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 6800c81d4..ad144877b 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -75,6 +75,7 @@ test-suite "serialization" : [ test-bsl-run_files test_derived_class_ptr : A ] [ test-bsl-run_files test_diamond ] [ test-bsl-run_files test_diamond_complex ] + [ test-bsl-run_files test_enum ] [ test-bsl-run_files test_filesystem_path ] [ test-bsl-run_files test_forward_list : A : : [ requires cxx11_hdr_forward_list ] ] # BOOST_NO_CXX11_HDR_FORWARD_LIST [ test-bsl-run_files test_forward_list_ptrs : A : : [ requires cxx11_hdr_forward_list ] ] # BOOST_NO_CXX11_HDR_FORWARD_LIST diff --git a/test/test_enum.cpp b/test/test_enum.cpp new file mode 100644 index 000000000..058f57492 --- /dev/null +++ b/test/test_enum.cpp @@ -0,0 +1,93 @@ +/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 +// test_enum.cpp + +// Copyright 2026 Gennaro Prota +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// Tests serialization of enumerators. In particular it checks that a value of +// a scoped enum whose underlying type is wider than int round trips at full +// width, rather than being truncated to int on save. + +#include +#include +#include + +#include + +#if defined(BOOST_NO_STDC_NAMESPACE) +namespace std{ + using ::remove; +} +#endif + +#include +#include "test_tools.hpp" + +// underlying type is int, so the archived form is unchanged from earlier +// releases of the library +enum color { red, green = 3, blue = 100 }; + +#ifndef BOOST_NO_CXX11_SCOPED_ENUMS + +// the value 2^32 does not fit in a 32 bit int; a plain cast to int on save +// used to truncate it +enum class wide : long long { + one = 1, + big = 1LL << 32 +}; + +// a narrow underlying type: still stored through int, so unchanged on the +// wire, but it must round trip +enum class narrow : signed char { + minus_one = -1, + two = 2, + hundred = 100 +}; + +#endif // BOOST_NO_CXX11_SCOPED_ENUMS + +int test_main(int /* argc */, char * /* argv */[]){ + const char * testfile = boost::archive::tmpnam(NULL); + BOOST_REQUIRE(NULL != testfile); + + const color c_save = blue; + #ifndef BOOST_NO_CXX11_SCOPED_ENUMS + const wide w_save = wide::big; + const narrow n_save = narrow::minus_one; + #endif + { + test_ostream os(testfile, TEST_STREAM_FLAGS); + test_oarchive oa(os, TEST_ARCHIVE_FLAGS); + oa << boost::serialization::make_nvp("c", c_save); + #ifndef BOOST_NO_CXX11_SCOPED_ENUMS + oa << boost::serialization::make_nvp("w", w_save); + oa << boost::serialization::make_nvp("n", n_save); + #endif + } + color c_load = red; + #ifndef BOOST_NO_CXX11_SCOPED_ENUMS + wide w_load = wide::one; + narrow n_load = narrow::two; + #endif + { + test_istream is(testfile, TEST_STREAM_FLAGS); + test_iarchive ia(is, TEST_ARCHIVE_FLAGS); + ia >> boost::serialization::make_nvp("c", c_load); + #ifndef BOOST_NO_CXX11_SCOPED_ENUMS + ia >> boost::serialization::make_nvp("w", w_load); + ia >> boost::serialization::make_nvp("n", n_load); + #endif + } + BOOST_CHECK(c_save == c_load); + #ifndef BOOST_NO_CXX11_SCOPED_ENUMS + BOOST_CHECK(w_save == w_load); + // the point of the test: the value survived without being truncated + BOOST_CHECK(static_cast(w_load) == (1LL << 32)); + BOOST_CHECK(n_save == n_load); + #endif + + std::remove(testfile); + return EXIT_SUCCESS; +}