Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions doc/strong_typedef.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,25 @@ <h3>Usage</h3>
<code style="white-space: normal">typedef</code>. So

<pre><code>
BOOST_STRONG_TYPEDEF(primitive type, name)
BOOST_STRONG_TYPEDEF(type, name)
</code></pre>

will create a new type "name" which will be substitutable for the original
type but still of distinct type.
<p>
The wrapped type need not be a primitive one. It has to be default
constructible, copyable and assignable, and to provide
<code style="white-space: normal">==</code> and
<code style="white-space: normal">&lt;</code>, from which the remaining
comparisons are generated.

<h3>Implemenation</h3>
<code style="white-space: normal">BOOST_STRONG_TYPEDEF</code> 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.

<hr>
<p><i>&copy; Copyright <a href="http://www.rrsd.com">Robert Ramey</a> 2002-2004.
Expand Down
7 changes: 7 additions & 0 deletions include/boost/serialization/strong_typedef.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
#include <boost/type_traits/has_nothrow_assign.hpp>
#include <boost/type_traits/has_nothrow_constructor.hpp>
#include <boost/type_traits/has_nothrow_copy.hpp>
#include <boost/type_traits/is_nothrow_move_assignable.hpp>
#include <boost/type_traits/is_nothrow_move_constructible.hpp>

#include <utility>

#define BOOST_STRONG_TYPEDEF(T, D) \
struct D \
Expand All @@ -39,7 +43,10 @@ struct D
explicit D(const T& t_) BOOST_NOEXCEPT_IF(boost::has_nothrow_copy_constructor<T>::value) : t(t_) {} \
D() BOOST_NOEXCEPT_IF(boost::has_nothrow_default_constructor<T>::value) : t() {} \
D(const D & t_) BOOST_NOEXCEPT_IF(boost::has_nothrow_copy_constructor<T>::value) : t(t_.t) {} \
D(D && t_) BOOST_NOEXCEPT_IF(boost::is_nothrow_move_constructible<T>::value) : t(std::move(t_.t)) {} \
D& operator=(const D& rhs) BOOST_NOEXCEPT_IF(boost::has_nothrow_assign<T>::value) {t = rhs.t; return *this;} \
D& operator=(D&& rhs) BOOST_NOEXCEPT_IF(boost::is_nothrow_move_assignable<T>::value) \
{t = std::move(rhs.t); return *this;} \
D& operator=(const T& rhs) BOOST_NOEXCEPT_IF(boost::has_nothrow_assign<T>::value) {t = rhs; return *this;} \
operator const T&() const {return t;} \
operator T&() {return t;} \
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ if ! $(BOOST_ARCHIVE_LIST) {
# [ test-bsl-run test_dll_plugin : : dll_derived2 : <link>static:<build>no <target-os>linux:<linkflags>-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 ]
Expand Down
123 changes: 123 additions & 0 deletions test/test_strong_typedef_move.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/core/lightweight_test.hpp>
#include <boost/serialization/strong_typedef.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_nothrow_move_assignable.hpp>
#include <boost/type_traits/is_nothrow_move_constructible.hpp>

#include <utility>

// 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<strong_int>::value);
BOOST_STATIC_ASSERT(boost::is_nothrow_move_assignable<strong_int>::value);
BOOST_STATIC_ASSERT(! boost::is_nothrow_move_constructible<strong_counted>::value);
BOOST_STATIC_ASSERT(! boost::is_nothrow_move_assignable<strong_counted>::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();
}