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();
+}