Skip to content
Open
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
3 changes: 2 additions & 1 deletion docs/spec/proxy/friend_swap.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Function `swap` (`proxy<F>`)

```cpp
friend void swap(proxy& lhs, proxy& rhs) noexcept(noexcept(lhs.swap(rhs)));
friend void swap(proxy& lhs, proxy& rhs) noexcept(noexcept(lhs.swap(rhs)))
requires(requires { lhs.swap(rhs); });
```

Overloads the [std::swap](https://en.cppreference.com/w/cpp/algorithm/swap) algorithm for `proxy`. Exchanges the state of `lhs` with that of `rhs`. Effectively calls `lhs.swap(rhs)`.
Expand Down
13 changes: 13 additions & 0 deletions docs/spec/proxy/swap.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
# `proxy::swap`

```cpp
// (1)
void swap(proxy& rhs)
noexcept(F::relocatability >= constraint_level::nothrow ||
F::copyability == constraint_level::trivial)
requires(F::relocatability >= constraint_level::nontrivial ||
F::copyability == constraint_level::trivial);

// (2) (since 5.0.0)
void swap(proxy& rhs)
noexcept(F::copyability >= constraint_level::nothrow &&
F::destructibility >= constraint_level::nothrow)
requires(F::relocatability == constraint_level::none &&
(F::copyability == constraint_level::nontrivial ||
F::copyability == constraint_level::nothrow) &&
F::destructibility >= constraint_level::nontrivial);
```

Exchanges the contained values of `*this` and `rhs`.

- `(1)` Exchanges the values by relocation, or by exchanging the underlying storage when `F::relocatability == constraint_level::trivial` or `F::copyability == constraint_level::trivial` is `true`. If the relocation throws when `F::relocatability == constraint_level::nontrivial`, both operands can be left without a value.
- `(2)` Exchanges the values by copying, for a facade that forbids relocation. If a copy throws when `F::copyability == constraint_level::nontrivial`, one of the two operands can be left without a value.
33 changes: 31 additions & 2 deletions include/proxy/v4/detail/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1129,8 +1129,11 @@ class proxy : public detail::facade_traits<F>::direct_accessor,
if constexpr (F::copyability == constraint_level::nothrow) {
destroy();
initialize(rhs);
} else {
} else if constexpr (F::relocatability >= constraint_level::nontrivial) {
*this = proxy{rhs};
} else {
reset();
initialize(rhs);
}
}
return *this;
Expand Down Expand Up @@ -1161,8 +1164,12 @@ class proxy : public detail::facade_traits<F>::direct_accessor,
if constexpr (std::is_nothrow_constructible_v<std::decay_t<P>, P>) {
destroy();
initialize<std::decay_t<P>>(std::forward<P>(ptr));
} else {
} else if constexpr (F::relocatability >= constraint_level::nontrivial ||
F::copyability >= constraint_level::nothrow) {
*this = proxy{std::forward<P>(ptr)};
} else {
reset();
initialize<std::decay_t<P>>(std::forward<P>(ptr));
}
return *this;
}
Expand Down Expand Up @@ -1217,6 +1224,28 @@ class proxy : public detail::facade_traits<F>::direct_accessor,
}
}
}
void swap(proxy& rhs) noexcept(F::copyability >= constraint_level::nothrow &&
F::destructibility >=
constraint_level::nothrow)
requires(F::relocatability == constraint_level::none &&
(F::copyability == constraint_level::nontrivial ||
F::copyability == constraint_level::nothrow) &&
F::destructibility >= constraint_level::nontrivial)
{
if (meta_.has_value()) {
if (rhs.meta_.has_value()) {
proxy temp = *this;
*this = rhs;
rhs = temp;
} else {
rhs = *this;
reset();
}
} else if (rhs.meta_.has_value()) {
*this = rhs;
rhs.reset();
}
}
template <class P, class... Args>
constexpr P& emplace(Args&&... args) noexcept(
std::is_nothrow_constructible_v<P, Args...> &&
Expand Down
201 changes: 201 additions & 0 deletions tests/proxy_lifetime_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,207 @@ TEST(ProxyLifetimeTests, TestSwap_Trivial) {
ASSERT_EQ(ToString(*p2), "123");
}

TEST(ProxyLifetimeTests, Test_CopyAssignment_NoRelocation) {
struct Pinned : pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nontrivial> //
::support_relocation<pro::constraint_level::none> //
::build {};
int v1 = 111, v2 = 222;
pro::proxy<Pinned> p1{utils::ThrowingCopyPtr<int>{&v1}};
pro::proxy<Pinned> p2{utils::ThrowingCopyPtr<int>{&v2}};
p1 = p2;
ASSERT_EQ(ToString(*p1), "222");
ASSERT_EQ(ToString(*p2), "222");
p1 = utils::ThrowingCopyPtr<int>{&v1};
ASSERT_EQ(ToString(*p1), "111");
}

TEST(ProxyLifetimeTests, Test_PointerAssignment_NoRelocationNoCopy) {
struct Pinned : pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_relocation<pro::constraint_level::none> //
::build {};
int v1 = 111, v2 = 222;
pro::proxy<Pinned> p{utils::ThrowingCopyPtr<int>{&v1}};
p = utils::ThrowingCopyPtr<int>{&v2};
ASSERT_EQ(ToString(*p), "222");
}

TEST(ProxyLifetimeTests, Test_PointerAssignment_ThrowingInitialization) {
struct Movable : pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nontrivial> //
::support_relocation<pro::constraint_level::nontrivial> //
::build {};
struct TriviallyCopyable
: pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::trivial> //
::support_relocation<pro::constraint_level::none> //
::build {};
struct NothrowCopyable : pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nothrow> //
::support_relocation<pro::constraint_level::none> //
::build {};
int v1 = 111, v2 = 222;

pro::proxy<Movable> p1{std::in_place_type<utils::ThrowOnMovePtr<int>>, &v1};
ASSERT_THROW(p1 = utils::ThrowOnMovePtr<int>{&v2},
utils::ConstructionFailure);
ASSERT_TRUE(p1.has_value());
ASSERT_EQ(ToString(*p1), "111");

pro::proxy<TriviallyCopyable> p2{
std::in_place_type<utils::ThrowOnMovePtr<int>>, &v1};
ASSERT_THROW(p2 = utils::ThrowOnMovePtr<int>{&v2},
utils::ConstructionFailure);
ASSERT_TRUE(p2.has_value());
ASSERT_EQ(ToString(*p2), "111");

pro::proxy<NothrowCopyable> p3{std::in_place_type<utils::ThrowOnMovePtr<int>>,
&v1};
ASSERT_THROW(p3 = utils::ThrowOnMovePtr<int>{&v2},
utils::ConstructionFailure);
ASSERT_TRUE(p3.has_value());
ASSERT_EQ(ToString(*p3), "111");
}

TEST(ProxyLifetimeTests, TestSwap_NoRelocation) {
struct Pinned : pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nothrow> //
::support_relocation<pro::constraint_level::none> //
::build {};
struct PinnedThrowingDestruction
: pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nothrow> //
::support_relocation<pro::constraint_level::none> //
::support_destruction<pro::constraint_level::nontrivial> //
::build {};

int v1 = 111, v2 = 222;
pro::proxy<Pinned> p1 = &v1;
pro::proxy<Pinned> p2 = &v2;
static_assert(noexcept(p1.swap(p2)));
p1.swap(p2);
ASSERT_EQ(ToString(*p1), "222");
ASSERT_EQ(ToString(*p2), "111");
swap(p1, p2);
ASSERT_EQ(ToString(*p1), "111");
ASSERT_EQ(ToString(*p2), "222");
swap(p1, p1);
ASSERT_EQ(ToString(*p1), "111");

pro::proxy<Pinned> p3;
swap(p1, p3);
ASSERT_FALSE(p1.has_value());
ASSERT_EQ(ToString(*p3), "111");
swap(p1, p3);
ASSERT_EQ(ToString(*p1), "111");
ASSERT_FALSE(p3.has_value());
swap(p3, p3);
ASSERT_FALSE(p3.has_value());
pro::proxy<Pinned> p4;
swap(p3, p4);
ASSERT_FALSE(p3.has_value());
ASSERT_FALSE(p4.has_value());

pro::proxy<PinnedThrowingDestruction> r1 = &v1;
pro::proxy<PinnedThrowingDestruction> r2 = &v2;
static_assert(!noexcept(r1.swap(r2)));
swap(r1, r2);
ASSERT_EQ(ToString(*r1), "222");
ASSERT_EQ(ToString(*r2), "111");
}

TEST(ProxyLifetimeTests, TestSwap_NoRelocation_ThrowingCopy) {
struct PinnedThrowingCopy
: pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nontrivial> //
::support_relocation<pro::constraint_level::none> //
::build {};
utils::LifetimeTracker tracker;
pro::proxy<PinnedThrowingCopy> p1{
std::in_place_type<utils::LifetimeTracker::Session>, &tracker};
pro::proxy<PinnedThrowingCopy> p2{
std::in_place_type<utils::LifetimeTracker::Session>, &tracker};
static_assert(!noexcept(p1.swap(p2)));
swap(p1, p2);
ASSERT_EQ(ToString(*p1), "Session 4");
ASSERT_EQ(ToString(*p2), "Session 5");
tracker.ThrowOnNextConstruction();
ASSERT_THROW(swap(p1, p2), utils::ConstructionFailure);
ASSERT_EQ(ToString(*p1), "Session 4");
ASSERT_EQ(ToString(*p2), "Session 5");
}

TEST(ProxyLifetimeTests, TestSwap_NoRelocation_Null) {
struct PinnedThrowingCopy
: pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nontrivial> //
::support_relocation<pro::constraint_level::none> //
::build {};
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
pro::proxy<PinnedThrowingCopy> p1{
std::in_place_type<utils::LifetimeTracker::Session>, &tracker};
expected_ops.emplace_back(1,
utils::LifetimeOperationType::kValueConstruction);
pro::proxy<PinnedThrowingCopy> p2;
swap(p1, p2);
ASSERT_FALSE(p1.has_value());
ASSERT_TRUE(p2.has_value());
ASSERT_EQ(ToString(*p2), "Session 2");
expected_ops.emplace_back(2,
utils::LifetimeOperationType::kCopyConstruction);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);

swap(p1, p2);
ASSERT_TRUE(p1.has_value());
ASSERT_EQ(ToString(*p1), "Session 3");
ASSERT_FALSE(p2.has_value());
expected_ops.emplace_back(3,
utils::LifetimeOperationType::kCopyConstruction);
expected_ops.emplace_back(2, utils::LifetimeOperationType::kDestruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
expected_ops.emplace_back(3, utils::LifetimeOperationType::kDestruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}

TEST(ProxyLifetimeTests, TestSwap_PrefersRelocation) {
struct Relocatable
: pro::facade_builder //
::add_convention<utils::spec::FreeToString,
std::string() const> //
::support_copy<pro::constraint_level::nothrow> //
::support_relocation<pro::constraint_level::nontrivial> //
::build {};
int v1 = 111, v2 = 222;
pro::proxy<Relocatable> p1 = &v1;
pro::proxy<Relocatable> p2 = &v2;
static_assert(!noexcept(p1.swap(p2)));
swap(p1, p2);
ASSERT_EQ(ToString(*p1), "222");
ASSERT_EQ(ToString(*p2), "111");
}

TEST(ProxyLifetimeTests, Test_DirectConvension_Lvalue) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
Expand Down
32 changes: 32 additions & 0 deletions tests/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,38 @@ class LifetimeTracker {
std::vector<LifetimeOperation> ops_;
};

template <class T>
class ThrowingCopyPtr {
public:
using element_type = T;

explicit ThrowingCopyPtr(T* ptr) noexcept : ptr_(ptr) {}
ThrowingCopyPtr(const ThrowingCopyPtr& rhs) : ptr_(rhs.ptr_) {}
ThrowingCopyPtr& operator=(const ThrowingCopyPtr&) = default;
T& operator*() const noexcept { return *ptr_; }

private:
T* ptr_;
};

template <class T>
class ThrowOnMovePtr {
public:
using element_type = T;

explicit ThrowOnMovePtr(T* ptr) noexcept : ptr_(ptr) {}
ThrowOnMovePtr(const ThrowOnMovePtr&) = default;
ThrowOnMovePtr(ThrowOnMovePtr&& rhs) : ptr_(rhs.ptr_) {
if (ptr_ != nullptr) {
throw ConstructionFailure{LifetimeOperationType::kValueConstruction};
}
}
T& operator*() const noexcept { return *ptr_; }

private:
T* ptr_;
};

namespace spec {

using std::to_string;
Expand Down
Loading