Skip to content

Commit 1fd935d

Browse files
authored
Merge pull request #71 from bluescarni/pr/better_invalid
Better semantics for the invalid state
2 parents b484586 + 39e816e commit 1fd935d

File tree

7 files changed

+149
-45
lines changed

7 files changed

+149
-45
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ tanuki
1010
<br />
1111
<p align="center">
1212
<p align="center">
13-
A type-erasure toolkit for C++20
13+
A type-erasure toolkit for C++20/23
1414
<br />
1515
<a href="https://bluescarni.github.io/tanuki/index.html"><strong>Explore the docs »</strong></a>
1616
<br />

doc/config.rst

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,27 @@ Configuration options
4949

5050
This option selects the semantics for the :cpp:class:`wrap` class.
5151

52-
.. cpp:var:: bool copyable = true
52+
.. cpp:var:: bool copy_constructible = true
5353

54-
This option selects whether or not the :cpp:class:`wrap` class is copy constructible/assignable.
54+
This option selects whether or not the :cpp:class:`wrap` class is copy-constructible.
5555

5656
This option is ignored when employing :ref:`reference semantics <ref_semantics>`.
5757

58-
.. cpp:var:: bool movable = true
58+
.. cpp:var:: bool copy_assignable = true
5959

60-
This option selects whether or not the :cpp:class:`wrap` class is move constructible/assignable.
60+
This option selects whether or not the :cpp:class:`wrap` class is copy-assignable.
6161

6262
This option is ignored when employing :ref:`reference semantics <ref_semantics>`.
6363

64-
.. cpp:var:: bool swappable = true
64+
.. cpp:var:: bool move_constructible = true
6565

66-
This option selects whether or not the :cpp:class:`wrap` class is swappable.
66+
This option selects whether or not the :cpp:class:`wrap` class is move-constructible.
67+
68+
This option is ignored when employing :ref:`reference semantics <ref_semantics>`.
69+
70+
.. cpp:var:: bool move_assignable = true
71+
72+
This option selects whether or not the :cpp:class:`wrap` class is move-assignable.
6773

6874
This option is ignored when employing :ref:`reference semantics <ref_semantics>`.
6975

@@ -132,7 +138,7 @@ Configuration options
132138
- :cpp:var:`config::static_align` is a power of two,
133139
- :cpp:var:`config::explicit_ctor` is one of the enumerators defined in :cpp:enum:`wrap_ctor`,
134140
- :cpp:var:`config::semantics` is one of the enumerators defined in :cpp:enum:`wrap_semantics`,
135-
- if :cpp:var:`config::copyable` is set to ``true``, so is :cpp:var:`config::movable` (that is,
136-
a copyable :cpp:class:`wrap` must also be movable),
137-
- if :cpp:var:`config::movable` is set to ``true``, so is :cpp:var:`config::swappable` (that is,
138-
a movable :cpp:class:`wrap` must also be swappable).
141+
- if :cpp:var:`config::copy_assignable` is set to ``true``, so is :cpp:var:`config::copy_constructible` (that is,
142+
copy-assignability requires copy-constructibility),
143+
- if :cpp:var:`config::move_assignable` is set to ``true``, so is :cpp:var:`config::move_constructible` (that is,
144+
move-assignability requires move-constructibility).

doc/custom_construct.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ A :cpp:class:`wrap` object can become invalid in a variety of circumstances:
4949

5050
The only allowed operations on an invalid :cpp:class:`wrap` are:
5151

52-
- destruction,
52+
- copy/move construction/assignment (including via :cpp:func:`wrap::copy()`), swapping and destruction,
5353
- the invocation of :cpp:func:`~wrap::is_invalid()` and :cpp:func:`is_valid()`,
54-
- copy/move assignment from, and swapping with, a valid :cpp:class:`wrap`,
5554
- :ref:`emplacement <emplacement>`,
5655
- generic assignment.
5756

doc/wrap.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The ``wrap`` class
1717
- a non-``void`` default-initialisable :cpp:type:`~config::DefaultValueType` with a valid, default-initialisable
1818
interface implementation for :cpp:type:`IFace` has been specified as first template argument in :cpp:var:`Cfg`.
1919
In this case, the default constructor value-initialises an instance of :cpp:type:`~config::DefaultValueType` as
20-
the internal type-erased value. When employing value semantics, the copyability, movability and swappability of
20+
the internal type-erased value. When employing value semantics, the copyability and movability of
2121
:cpp:type:`~config::DefaultValueType` must be consistent with the corresponding settings
2222
in :cpp:var:`Cfg` in order for this constructor to be enabled.
2323

@@ -63,7 +63,7 @@ The ``wrap`` class
6363
- the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T`
6464
(see the :cpp:concept:`iface_with_impl` concept);
6565
- :cpp:var:`x` can be perfectly-forwarded to construct an instance of the value type;
66-
- when employing value semantics, the copyability, movability and swappability of the value type
66+
- when employing value semantics, the copyability and movability of the value type
6767
are consistent with the corresponding settings in :cpp:var:`Cfg`.
6868

6969
This constructor is marked ``explicit`` if either:
@@ -98,7 +98,7 @@ The ``wrap`` class
9898
- the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T`
9999
(see the :cpp:concept:`iface_with_impl` concept);
100100
- :cpp:var:`args` can be perfectly-forwarded to construct an instance of the value type :cpp:type:`T`;
101-
- when employing value semantics, the copyability, movability and swappability of the value type :cpp:type:`T`
101+
- when employing value semantics, the copyability and movability of the value type :cpp:type:`T`
102102
are consistent with the corresponding settings in :cpp:var:`Cfg`.
103103

104104
:param args: the input construction arguments.
@@ -167,7 +167,7 @@ The ``wrap`` class
167167
- an instance of :cpp:type:`T` can be constructed from :cpp:type:`Args`;
168168
- the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T`
169169
(see the :cpp:concept:`iface_with_impl` concept);
170-
- when employing value semantics, the copyability, movability and swappability of the value type :cpp:type:`T`
170+
- when employing value semantics, the copyability and movability of the value type :cpp:type:`T`
171171
are consistent with the corresponding settings in :cpp:var:`Cfg`.
172172

173173
Passing :cpp:var:`w` as an argument in :cpp:var:`args` (e.g., attempting to emplace :cpp:var:`w` into itself) will lead to

include/tanuki/tanuki.hpp

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,21 +1527,27 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage<IFace, Cfg.static_size,
15271527
}
15281528

15291529
private:
1530+
// Implementation of copy-initialisation for value semantics.
15301531
void copy_init_from(const wrap &other)
15311532
{
15321533
static_assert(Cfg.semantics == wrap_semantics::value);
15331534

1534-
if constexpr (Cfg.static_size == 0u) {
1535-
// Static storage disabled.
1536-
this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder();
1537-
} else {
1538-
if (other.stype()) {
1539-
// Other has static storage.
1540-
this->m_pv_iface = other.m_pv_iface->_tanuki_copy_init_holder(this->static_storage);
1541-
} else {
1542-
// Other has dynamic storage.
1535+
if (is_valid(other)) {
1536+
if constexpr (Cfg.static_size == 0u) {
1537+
// Static storage disabled.
15431538
this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder();
1539+
} else {
1540+
if (other.stype()) {
1541+
// Other has static storage.
1542+
this->m_pv_iface = other.m_pv_iface->_tanuki_copy_init_holder(this->static_storage);
1543+
} else {
1544+
// Other has dynamic storage.
1545+
this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder();
1546+
}
15441547
}
1548+
} else {
1549+
// Handle initialisation from an invalid wrap.
1550+
this->m_pv_iface = nullptr;
15451551
}
15461552
}
15471553

@@ -1560,34 +1566,42 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage<IFace, Cfg.static_size,
15601566
}
15611567

15621568
private:
1569+
// Implementation of move-initialisation for value semantics.
1570+
//
15631571
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
15641572
void move_init_from(wrap &&other) noexcept
15651573
{
15661574
static_assert(Cfg.semantics == wrap_semantics::value);
15671575

1568-
if constexpr (Cfg.static_size == 0u) {
1569-
// Static storage disabled. Shallow copy the pointer.
1570-
this->m_pv_iface = other.m_pv_iface;
1571-
1572-
// Invalidate other.
1573-
other.m_pv_iface = nullptr;
1574-
} else {
1575-
auto *pv_iface = other.m_pv_iface;
1576-
1577-
if (other.stype()) {
1578-
// Other has static storage.
1579-
this->m_pv_iface = std::move(*pv_iface)._tanuki_move_init_holder(this->static_storage);
1580-
} else {
1581-
// Other has dynamic storage.
1582-
this->m_pv_iface = pv_iface;
1576+
if (is_valid(other)) {
1577+
if constexpr (Cfg.static_size == 0u) {
1578+
// Static storage disabled. Shallow copy the pointer.
1579+
this->m_pv_iface = other.m_pv_iface;
15831580

15841581
// Invalidate other.
15851582
other.m_pv_iface = nullptr;
1583+
} else {
1584+
auto *pv_iface = other.m_pv_iface;
1585+
1586+
if (other.stype()) {
1587+
// Other has static storage.
1588+
this->m_pv_iface = std::move(*pv_iface)._tanuki_move_init_holder(this->static_storage);
1589+
} else {
1590+
// Other has dynamic storage.
1591+
this->m_pv_iface = pv_iface;
1592+
1593+
// Invalidate other.
1594+
other.m_pv_iface = nullptr;
1595+
}
15861596
}
1597+
} else {
1598+
// Handle initialisation from an invalid wrap.
1599+
this->m_pv_iface = nullptr;
15871600
}
15881601
}
15891602

15901603
public:
1604+
// Move constructor.
15911605
wrap(wrap &&other) noexcept
15921606
requires(Cfg.move_constructible || Cfg.semantics == wrap_semantics::reference)
15931607
&& std::default_initializable<ref_iface_t>
@@ -1635,13 +1649,22 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage<IFace, Cfg.static_size,
16351649
return *this;
16361650
}
16371651

1638-
// Handle invalid object.
1652+
// Handle invalid this.
16391653
if (is_invalid(*this)) {
16401654
// No need to destroy, just move-init from other is sufficient.
1655+
//
1656+
// NOTE: move_init_from() will work fine if other is invalid.
16411657
move_init_from(std::move(other));
16421658
return *this;
16431659
}
16441660

1661+
// this is valid, handle invalid other.
1662+
if (is_invalid(other)) {
1663+
destroy();
1664+
this->m_pv_iface = nullptr;
1665+
return *this;
1666+
}
1667+
16451668
// Helper to implement move-assignment via destruction + move-initialisation.
16461669
const auto destroy_and_move_init = [this, &other]() noexcept {
16471670
destroy();
@@ -1696,15 +1719,24 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage<IFace, Cfg.static_size,
16961719
return *this;
16971720
}
16981721

1699-
// Handle invalid object.
1722+
// Handle invalid this.
17001723
if (is_invalid(*this)) {
17011724
// No need to destroy, just copy-init from other is sufficient.
17021725
//
17031726
// NOTE: copy_init_from() either succeeds or fails, no intermediate state is possible.
1727+
//
1728+
// NOTE: copy_init_from() will work fine if other is invalid.
17041729
copy_init_from(other);
17051730
return *this;
17061731
}
17071732

1733+
// this is valid, handle invalid other.
1734+
if (is_invalid(other)) {
1735+
destroy();
1736+
this->m_pv_iface = nullptr;
1737+
return *this;
1738+
}
1739+
17081740
// NOTE: the idea here is as follows:
17091741
//
17101742
// - if the internal types are the same and copy-assignment is possible, employ it; otherwise,
@@ -1734,7 +1766,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage<IFace, Cfg.static_size,
17341766
return *this;
17351767
}
17361768

1737-
// Assignment to the invalid state.
1769+
// Assignment from the invalid state.
17381770
wrap &operator=(invalid_wrap_t) noexcept
17391771
{
17401772
if constexpr (Cfg.semantics == wrap_semantics::value) {
@@ -2036,7 +2068,12 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage<IFace, Cfg.static_size,
20362068
requires(Cfg.semantics == wrap_semantics::reference)
20372069
{
20382070
wrap retval(invalid_wrap);
2039-
retval.m_pv_iface = w.m_pv_iface->_tanuki_shared_clone_holder();
2071+
// NOTE: perform the deep copy only if w is valid. Otherwise, return an invalid wrap.
2072+
if (is_valid(w)) {
2073+
retval.m_pv_iface = w.m_pv_iface->_tanuki_shared_clone_holder();
2074+
} else {
2075+
;
2076+
}
20402077
return retval;
20412078
}
20422079

test/test_basics.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,13 @@ TEST_CASE("basics")
182182
// Large one.
183183
auto w2copy = w2;
184184
REQUIRE(value_ref<large>(w2copy).buffer[0] == 2);
185+
// Construction from invalid wrap.
186+
{
187+
const wrap_t inv_wrap(tanuki::invalid_wrap);
188+
// NOLINTNEXTLINE
189+
const auto inv_wrap_copy(inv_wrap);
190+
REQUIRE(!is_valid(inv_wrap_copy));
191+
}
185192

186193
// Move constructing.
187194
// Small one.
@@ -197,6 +204,8 @@ TEST_CASE("basics")
197204
REQUIRE(is_invalid(w2));
198205
// NOLINTEND
199206
REQUIRE(has_dynamic_storage(w2));
207+
// Construction from invalid wrap.
208+
REQUIRE(!is_valid(wrap_t(wrap_t(tanuki::invalid_wrap))));
200209

201210
// Emplace test with class which is not copyable/movable/swappable.
202211
wrap<any_iface, tanuki::config<>{.copy_constructible = false,
@@ -370,6 +379,36 @@ TEST_CASE("assignment")
370379
// NOLINTNEXTLINE
371380
REQUIRE(!is_invalid(w2));
372381
}
382+
383+
// Move assignment between invalid objects.
384+
{
385+
wrap_t w1{tanuki::invalid_wrap}, w2{tanuki::invalid_wrap};
386+
w1 = std::move(w2);
387+
REQUIRE(is_invalid(w1));
388+
// NOLINTNEXTLINE
389+
REQUIRE(is_invalid(w2));
390+
}
391+
{
392+
wrap_t w1{large{.str = "blif"}}, w2{tanuki::invalid_wrap};
393+
w1 = std::move(w2);
394+
REQUIRE(is_invalid(w1));
395+
// NOLINTNEXTLINE
396+
REQUIRE(is_invalid(w2));
397+
}
398+
399+
// Copy assignment between invalid objects.
400+
{
401+
wrap_t w1{tanuki::invalid_wrap}, w2{tanuki::invalid_wrap};
402+
w1 = w2;
403+
REQUIRE(is_invalid(w1));
404+
REQUIRE(is_invalid(w2));
405+
}
406+
{
407+
wrap_t w1{large{.str = "blif"}}, w2{tanuki::invalid_wrap};
408+
w1 = w2;
409+
REQUIRE(is_invalid(w1));
410+
REQUIRE(is_invalid(w2));
411+
}
373412
}
374413

375414
// NOTE: a small test to check the cleanup logic in the copy assignment operator of wrap when the assignment is

test/test_ref_semantics.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ TEST_CASE("basics")
115115
REQUIRE(value_isa<int>(w6));
116116
REQUIRE(value_ref<int>(w6) == 11);
117117
REQUIRE(tanuki::value_ptr<int>(w5) == tanuki::value_ptr<int>(w6));
118+
// NOLINTNEXTLINE
119+
auto w6a(w1);
120+
REQUIRE(is_invalid(w6a));
118121

119122
// Move construction.
120123
REQUIRE(std::is_nothrow_copy_constructible_v<wrap2_t>);
@@ -123,6 +126,7 @@ TEST_CASE("basics")
123126
REQUIRE(value_isa<int>(w7));
124127
REQUIRE(value_ref<int>(w7) == 11);
125128
REQUIRE(tanuki::value_ptr<int>(w5) == tanuki::value_ptr<int>(w7));
129+
REQUIRE(is_invalid(wrap1_t(wrap1_t(tanuki::invalid_wrap))));
126130

127131
// Move assignment.
128132
REQUIRE(std::is_nothrow_move_assignable_v<wrap2_t>);
@@ -132,6 +136,14 @@ TEST_CASE("basics")
132136
REQUIRE(value_isa<int>(w8));
133137
REQUIRE(value_ref<int>(w8) == 11);
134138
REQUIRE(tanuki::value_ptr<int>(w5) == tanuki::value_ptr<int>(w8));
139+
// Move assignment between invalid objects.
140+
{
141+
wrap2_t winv1{tanuki::invalid_wrap}, winv2{tanuki::invalid_wrap};
142+
winv1 = std::move(winv2);
143+
REQUIRE(is_invalid(winv1));
144+
// NOLINTNEXTLINE
145+
REQUIRE(is_invalid(winv2));
146+
}
135147

136148
// Copy assignment.
137149
REQUIRE(std::is_nothrow_copy_assignable_v<wrap2_t>);
@@ -141,6 +153,13 @@ TEST_CASE("basics")
141153
REQUIRE(value_isa<int>(w9));
142154
REQUIRE(value_ref<int>(w9) == 11);
143155
REQUIRE(tanuki::value_ptr<int>(w5) == tanuki::value_ptr<int>(w9));
156+
// Copy assignment between invalid objects.
157+
{
158+
wrap2_t winv1{tanuki::invalid_wrap}, winv2{tanuki::invalid_wrap};
159+
winv1 = winv2;
160+
REQUIRE(is_invalid(winv1));
161+
REQUIRE(is_invalid(winv2));
162+
}
144163

145164
// Generic assignment.
146165
wrap2_t w10;
@@ -222,6 +241,10 @@ TEST_CASE("basics")
222241

223242
const wrap2_t w13(noncopyable{});
224243
REQUIRE_THROWS_MATCHES(copy(w13), std::invalid_argument, Message("Attempting to clone a non-copyable value type"));
244+
245+
// Deep copy of invalid wrap.
246+
const wrap2_t w14(tanuki::invalid_wrap);
247+
REQUIRE(is_invalid(copy(w14)));
225248
}
226249

227250
#if defined(TANUKI_WITH_BOOST_S11N)

0 commit comments

Comments
 (0)