From a6c698325b541623cfcdedb2a46a429c7bf1cc9d Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Tue, 18 Aug 2026 09:56:10 +0200 Subject: [PATCH] Add move support to BOOST_STRONG_TYPEDEF Declaring a copy constructor and a copy assignment operator stops the compiler from declaring the move ones, so moving a strong typedef copied the value it wrapped. The documentation said the macro was for primitive types, which would make this pointless, but the implementation has taken class types since the `noexcept` specifications went in. Closes #341. --- doc/strong_typedef.html | 16 ++- .../boost/serialization/strong_typedef.hpp | 7 + test/Jamfile.v2 | 1 + test/test_strong_typedef_move.cpp | 123 ++++++++++++++++++ 4 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 test/test_strong_typedef_move.cpp diff --git a/doc/strong_typedef.html b/doc/strong_typedef.html index 391d7d1d2..bc141102c 100644 --- a/doc/strong_typedef.html +++ b/doc/strong_typedef.html @@ -70,17 +70,25 @@

Usage

typedef. So

-BOOST_STRONG_TYPEDEF(primitive type, name)
+BOOST_STRONG_TYPEDEF(type, name)
 
will create a new type "name" which will be substitutable for the original type but still of distinct type. +

+The wrapped type need not be a primitive one. It has to be default +constructible, copyable and assignable, and to provide +== and +<, from which the remaining +comparisons are generated.

Implemenation

BOOST_STRONG_TYPEDEF is a macro -which generates a class named "name" which wraps an instance of its -primitive type and provides appropriate conversion operators in order -to make the new type substitutable for the one that it wraps. +which generates a class named "name" which wraps an instance of the +type and provides appropriate conversion operators in order +to make the new type substitutable for the one that it wraps. The +generated class copies and moves as the wrapped type does, so wrapping a +type which is cheaper to move than to copy costs nothing.

© Copyright Robert Ramey 2002-2004. diff --git a/include/boost/serialization/strong_typedef.hpp b/include/boost/serialization/strong_typedef.hpp index 54d7d01de..e7ea05528 100644 --- a/include/boost/serialization/strong_typedef.hpp +++ b/include/boost/serialization/strong_typedef.hpp @@ -28,6 +28,10 @@ #include #include #include +#include +#include + +#include #define BOOST_STRONG_TYPEDEF(T, D) \ struct D \ @@ -39,7 +43,10 @@ struct D explicit D(const T& t_) BOOST_NOEXCEPT_IF(boost::has_nothrow_copy_constructor::value) : t(t_) {} \ D() BOOST_NOEXCEPT_IF(boost::has_nothrow_default_constructor::value) : t() {} \ D(const D & t_) BOOST_NOEXCEPT_IF(boost::has_nothrow_copy_constructor::value) : t(t_.t) {} \ + D(D && t_) BOOST_NOEXCEPT_IF(boost::is_nothrow_move_constructible::value) : t(std::move(t_.t)) {} \ D& operator=(const D& rhs) BOOST_NOEXCEPT_IF(boost::has_nothrow_assign::value) {t = rhs.t; return *this;} \ + D& operator=(D&& rhs) BOOST_NOEXCEPT_IF(boost::is_nothrow_move_assignable::value) \ + {t = std::move(rhs.t); return *this;} \ D& operator=(const T& rhs) BOOST_NOEXCEPT_IF(boost::has_nothrow_assign::value) {t = rhs; return *this;} \ operator const T&() const {return t;} \ operator T&() {return t;} \ diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 965d8dabb..02f55e681 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -159,6 +159,7 @@ if ! $(BOOST_ARCHIVE_LIST) { # [ test-bsl-run test_dll_plugin : : dll_derived2 : static:no linux:-ldl ] [ test-bsl-run test_duplicate_type_registration ] + [ test-bsl-run test_strong_typedef_move ] [ test-bsl-run test_private_ctor ] [ test-bsl-run test_reset_object_address : A ] [ test-bsl-run test_void_cast ] diff --git a/test/test_strong_typedef_move.cpp b/test/test_strong_typedef_move.cpp new file mode 100644 index 000000000..8a2b7466b --- /dev/null +++ b/test/test_strong_typedef_move.cpp @@ -0,0 +1,123 @@ +/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 +// test_strong_typedef_move.cpp + +// 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) + +// See http://www.boost.org for updates, documentation, and revision history. + +// Declaring a copy constructor and a copy assignment operator stops the +// compiler from declaring the move ones, so a strong typedef used to copy +// the value it wraps where a move was asked for. That was silent: the code +// compiled and gave the right answer, only slowly, which is why it wants a +// test that counts the operations rather than one that merely compiles. + +// Reported by bweed-mathworks in +// https://github.com/boostorg/serialization/issues/341, with the two members +// to add. Thanks! + +#include +#include +#include +#include +#include + +#include + +// Says which of the two happened to it. The counters are global so that a +// check reads as "this many moves, no copies" without having to reason +// about which object ended up holding the tally. +struct counted { + static int copies; + static int moves; + counted(){} + counted(const counted &){ + ++copies; + } + counted(counted &&){ + ++moves; + } + counted & operator=(const counted &){ + ++copies; + return *this; + } + counted & operator=(counted &&){ + ++moves; + return *this; + } + bool operator==(const counted &) const { + return true; + } + bool operator<(const counted &) const { + return false; + } +}; + +int counted::copies = 0; +int counted::moves = 0; + +BOOST_STRONG_TYPEDEF(counted, strong_counted) +BOOST_STRONG_TYPEDEF(int, strong_int) + +static void reset(){ + counted::copies = 0; + counted::moves = 0; +} + +// A strong typedef of a type which can throw must not claim it cannot, and +// one of a type which cannot must not throw away the guarantee. +BOOST_STATIC_ASSERT(boost::is_nothrow_move_constructible::value); +BOOST_STATIC_ASSERT(boost::is_nothrow_move_assignable::value); +BOOST_STATIC_ASSERT(! boost::is_nothrow_move_constructible::value); +BOOST_STATIC_ASSERT(! boost::is_nothrow_move_assignable::value); + +static void test_move_construction(){ + reset(); + strong_counted a; + strong_counted b(std::move(a)); + (void) b; + BOOST_TEST_EQ(counted::moves, 1); + BOOST_TEST_EQ(counted::copies, 0); +} + +static void test_move_assignment(){ + reset(); + strong_counted a; + strong_counted b; + b = std::move(a); + BOOST_TEST_EQ(counted::moves, 1); + BOOST_TEST_EQ(counted::copies, 0); +} + +// Copying has to go on working, and go on being a copy. +static void test_copy_still_copies(){ + reset(); + strong_counted a; + strong_counted b(a); + strong_counted c; + c = a; + BOOST_TEST_EQ(counted::copies, 2); + BOOST_TEST_EQ(counted::moves, 0); +} + +// The value has to survive the move, not merely be moved. +static void test_value_is_carried(){ + strong_int a(42); + strong_int b(std::move(a)); + BOOST_TEST_EQ(b.t, 42); + + strong_int c; + c = std::move(b); + BOOST_TEST_EQ(c.t, 42); +} + +int +main(){ + test_move_construction(); + test_move_assignment(); + test_copy_still_copies(); + test_value_is_carried(); + return boost::report_errors(); +}