Skip to content

Commit 6829db7

Browse files
committed
[libc++] Implement copyable-box from Ranges
Differential Revision: https://reviews.llvm.org/D102135
1 parent 19885c7 commit 6829db7

File tree

12 files changed

+1080
-0
lines changed

12 files changed

+1080
-0
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ set(files
184184
__ranges/access.h
185185
__ranges/all.h
186186
__ranges/concepts.h
187+
__ranges/copyable_box.h
187188
__ranges/data.h
188189
__ranges/drop_view.h
189190
__ranges/empty_view.h
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP___RANGES_COPYABLE_BOX_H
11+
#define _LIBCPP___RANGES_COPYABLE_BOX_H
12+
13+
#include <__config>
14+
#include <__memory/addressof.h>
15+
#include <__memory/construct_at.h>
16+
#include <__utility/move.h>
17+
#include <concepts>
18+
#include <optional>
19+
#include <type_traits>
20+
21+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
22+
#pragma GCC system_header
23+
#endif
24+
25+
_LIBCPP_PUSH_MACROS
26+
#include <__undef_macros>
27+
28+
_LIBCPP_BEGIN_NAMESPACE_STD
29+
30+
#if !defined(_LIBCPP_HAS_NO_RANGES)
31+
32+
// __copyable_box allows turning a type that is copy-constructible (but maybe not copy-assignable) into
33+
// a type that is both copy-constructible and copy-assignable. It does that by introducing an empty state
34+
// and basically doing destroy-then-copy-construct in the assignment operator. The empty state is necessary
35+
// to handle the case where the copy construction fails after destroying the object.
36+
//
37+
// In some cases, we can completely avoid the use of an empty state; we provide a specialization of
38+
// __copyable_box that does this, see below for the details.
39+
40+
template<class _Tp>
41+
concept __copy_constructible_object = copy_constructible<_Tp> && is_object_v<_Tp>;
42+
43+
namespace ranges {
44+
// Primary template - uses std::optional and introduces an empty state in case assignment fails.
45+
template<__copy_constructible_object _Tp>
46+
class __copyable_box {
47+
[[no_unique_address]] optional<_Tp> __val_;
48+
49+
public:
50+
template<class ..._Args>
51+
requires is_constructible_v<_Tp, _Args...>
52+
_LIBCPP_HIDE_FROM_ABI
53+
constexpr explicit __copyable_box(in_place_t, _Args&& ...__args)
54+
noexcept(is_nothrow_constructible_v<_Tp, _Args...>)
55+
: __val_(in_place, _VSTD::forward<_Args>(__args)...)
56+
{ }
57+
58+
_LIBCPP_HIDE_FROM_ABI
59+
constexpr __copyable_box() noexcept(is_nothrow_default_constructible_v<_Tp>)
60+
requires default_initializable<_Tp>
61+
: __val_(in_place)
62+
{ }
63+
64+
_LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box const&) = default;
65+
_LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box&&) = default;
66+
67+
_LIBCPP_HIDE_FROM_ABI
68+
constexpr __copyable_box& operator=(__copyable_box const& __other)
69+
noexcept(is_nothrow_copy_constructible_v<_Tp>)
70+
{
71+
if (this != _VSTD::addressof(__other)) {
72+
if (__other.__has_value()) __val_.emplace(*__other);
73+
else __val_.reset();
74+
}
75+
return *this;
76+
}
77+
78+
_LIBCPP_HIDE_FROM_ABI
79+
__copyable_box& operator=(__copyable_box&&) requires movable<_Tp> = default;
80+
81+
_LIBCPP_HIDE_FROM_ABI
82+
constexpr __copyable_box& operator=(__copyable_box&& __other)
83+
noexcept(is_nothrow_move_constructible_v<_Tp>)
84+
{
85+
if (this != _VSTD::addressof(__other)) {
86+
if (__other.__has_value()) __val_.emplace(_VSTD::move(*__other));
87+
else __val_.reset();
88+
}
89+
return *this;
90+
}
91+
92+
_LIBCPP_HIDE_FROM_ABI constexpr _Tp const& operator*() const noexcept { return *__val_; }
93+
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() noexcept { return *__val_; }
94+
_LIBCPP_HIDE_FROM_ABI constexpr bool __has_value() const noexcept { return __val_.has_value(); }
95+
};
96+
97+
// This partial specialization implements an optimization for when we know we don't need to store
98+
// an empty state to represent failure to perform an assignment. For copy-assignment, this happens:
99+
//
100+
// 1. If the type is copyable (which includes copy-assignment), we can use the type's own assignment operator
101+
// directly and avoid using std::optional.
102+
// 2. If the type is not copyable, but it is nothrow-copy-constructible, then we can implement assignment as
103+
// destroy-and-then-construct and we know it will never fail, so we don't need an empty state.
104+
//
105+
// The exact same reasoning can be applied for move-assignment, with copyable replaced by movable and
106+
// nothrow-copy-constructible replaced by nothrow-move-constructible. This specialization is enabled
107+
// whenever we can apply any of these optimizations for both the copy assignment and the move assignment
108+
// operator.
109+
template<class _Tp>
110+
concept __doesnt_need_empty_state_for_copy = copyable<_Tp> || is_nothrow_copy_constructible_v<_Tp>;
111+
112+
template<class _Tp>
113+
concept __doesnt_need_empty_state_for_move = movable<_Tp> || is_nothrow_move_constructible_v<_Tp>;
114+
115+
template<__copy_constructible_object _Tp>
116+
requires __doesnt_need_empty_state_for_copy<_Tp> && __doesnt_need_empty_state_for_move<_Tp>
117+
class __copyable_box<_Tp> {
118+
[[no_unique_address]] _Tp __val_;
119+
120+
public:
121+
template<class ..._Args>
122+
requires is_constructible_v<_Tp, _Args...>
123+
_LIBCPP_HIDE_FROM_ABI
124+
constexpr explicit __copyable_box(in_place_t, _Args&& ...__args)
125+
noexcept(is_nothrow_constructible_v<_Tp, _Args...>)
126+
: __val_(_VSTD::forward<_Args>(__args)...)
127+
{ }
128+
129+
_LIBCPP_HIDE_FROM_ABI
130+
constexpr __copyable_box() noexcept(is_nothrow_default_constructible_v<_Tp>)
131+
requires default_initializable<_Tp>
132+
: __val_()
133+
{ }
134+
135+
_LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box const&) = default;
136+
_LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box&&) = default;
137+
138+
// Implementation of assignment operators in case we perform optimization (1)
139+
_LIBCPP_HIDE_FROM_ABI __copyable_box& operator=(__copyable_box const&) requires copyable<_Tp> = default;
140+
_LIBCPP_HIDE_FROM_ABI __copyable_box& operator=(__copyable_box&&) requires movable<_Tp> = default;
141+
142+
// Implementation of assignment operators in case we perform optimization (2)
143+
_LIBCPP_HIDE_FROM_ABI
144+
constexpr __copyable_box& operator=(__copyable_box const& __other) noexcept {
145+
static_assert(is_nothrow_copy_constructible_v<_Tp>);
146+
if (this != _VSTD::addressof(__other)) {
147+
_VSTD::destroy_at(_VSTD::addressof(__val_));
148+
_VSTD::construct_at(_VSTD::addressof(__val_), __other.__val_);
149+
}
150+
return *this;
151+
}
152+
153+
_LIBCPP_HIDE_FROM_ABI
154+
constexpr __copyable_box& operator=(__copyable_box&& __other) noexcept {
155+
static_assert(is_nothrow_move_constructible_v<_Tp>);
156+
if (this != _VSTD::addressof(__other)) {
157+
_VSTD::destroy_at(_VSTD::addressof(__val_));
158+
_VSTD::construct_at(_VSTD::addressof(__val_), _VSTD::move(__other.__val_));
159+
}
160+
return *this;
161+
}
162+
163+
_LIBCPP_HIDE_FROM_ABI constexpr _Tp const& operator*() const noexcept { return __val_; }
164+
_LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() noexcept { return __val_; }
165+
_LIBCPP_HIDE_FROM_ABI constexpr bool __has_value() const noexcept { return true; }
166+
};
167+
} // namespace ranges
168+
169+
#endif // !defined(_LIBCPP_HAS_NO_RANGES)
170+
171+
_LIBCPP_END_NAMESPACE_STD
172+
173+
_LIBCPP_POP_MACROS
174+
175+
#endif // _LIBCPP___RANGES_COPYABLE_BOX_H

libcxx/include/module.modulemap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,7 @@ module std [system] {
600600
module access { header "__ranges/access.h" }
601601
module all { header "__ranges/all.h" }
602602
module concepts { header "__ranges/concepts.h" }
603+
module copyable_box { header "__ranges/copyable_box.h" }
603604
module data { header "__ranges/data.h" }
604605
module empty { header "__ranges/empty.h" }
605606
module empty_view { header "__ranges/empty_view.h" }
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
// UNSUPPORTED: libcpp-no-concepts
11+
// UNSUPPORTED: gcc-10
12+
13+
// <copyable-box>& operator=(<copyable-box> const&)
14+
15+
// ADDITIONAL_COMPILE_FLAGS: -Wno-self-assign-overloaded
16+
17+
#include <__ranges/copyable_box.h>
18+
19+
#include <cassert>
20+
#include <type_traits>
21+
#include <utility> // in_place_t
22+
23+
#include "test_macros.h"
24+
#include "types.h"
25+
26+
constexpr bool test() {
27+
// Test the primary template
28+
{
29+
using Box = std::ranges::__copyable_box<CopyConstructible>;
30+
static_assert( std::is_copy_assignable_v<Box>);
31+
static_assert(!std::is_nothrow_copy_assignable_v<Box>);
32+
33+
{
34+
Box x(std::in_place, 5);
35+
Box const y(std::in_place, 10);
36+
Box& result = (x = y);
37+
38+
assert(&result == &x);
39+
assert(x.__has_value());
40+
assert(y.__has_value());
41+
assert((*x).value == 10);
42+
}
43+
// check self-assignment
44+
{
45+
Box x(std::in_place, 5);
46+
Box& result = (x = x);
47+
48+
assert(&result == &x);
49+
assert(x.__has_value());
50+
assert((*x).value == 5);
51+
}
52+
}
53+
54+
// Test optimization #1 for copy-assignment
55+
{
56+
using Box = std::ranges::__copyable_box<Copyable>;
57+
static_assert( std::is_copy_assignable_v<Box>);
58+
static_assert(!std::is_nothrow_copy_assignable_v<Box>);
59+
60+
{
61+
Box x(std::in_place, 5);
62+
Box const y(std::in_place, 10);
63+
Box& result = (x = y);
64+
65+
assert(&result == &x);
66+
assert(x.__has_value());
67+
assert(y.__has_value());
68+
assert((*x).value == 10);
69+
assert((*x).did_copy_assign);
70+
}
71+
// check self-assignment (should use the underlying type's assignment too)
72+
{
73+
Box x(std::in_place, 5);
74+
Box& result = (x = x);
75+
76+
assert(&result == &x);
77+
assert(x.__has_value());
78+
assert((*x).value == 5);
79+
assert((*x).did_copy_assign);
80+
}
81+
}
82+
83+
// Test optimization #2 for copy-assignment
84+
{
85+
using Box = std::ranges::__copyable_box<NothrowCopyConstructible>;
86+
static_assert(std::is_copy_assignable_v<Box>);
87+
static_assert(std::is_nothrow_copy_assignable_v<Box>);
88+
89+
{
90+
Box x(std::in_place, 5);
91+
Box const y(std::in_place, 10);
92+
Box& result = (x = y);
93+
94+
assert(&result == &x);
95+
assert(x.__has_value());
96+
assert(y.__has_value());
97+
assert((*x).value == 10);
98+
}
99+
// check self-assignment
100+
{
101+
Box x(std::in_place, 5);
102+
Box& result = (x = x);
103+
104+
assert(&result == &x);
105+
assert(x.__has_value());
106+
assert((*x).value == 5);
107+
}
108+
}
109+
110+
return true;
111+
}
112+
113+
// Tests for the empty state. Those can't be constexpr, since they are only reached
114+
// through throwing an exception.
115+
#if !defined(TEST_HAS_NO_EXCEPTIONS)
116+
void test_empty_state() {
117+
using Box = std::ranges::__copyable_box<ThrowsOnCopy>;
118+
119+
// assign non-empty to empty
120+
{
121+
Box x = create_empty_box();
122+
Box const y(std::in_place, 10);
123+
Box& result = (x = y);
124+
125+
assert(&result == &x);
126+
assert(x.__has_value());
127+
assert(y.__has_value());
128+
assert((*x).value == 10);
129+
}
130+
// assign empty to non-empty
131+
{
132+
Box x(std::in_place, 5);
133+
Box const y = create_empty_box();
134+
Box& result = (x = y);
135+
136+
assert(&result == &x);
137+
assert(!x.__has_value());
138+
assert(!y.__has_value());
139+
}
140+
// assign empty to empty
141+
{
142+
Box x = create_empty_box();
143+
Box const y = create_empty_box();
144+
Box& result = (x = y);
145+
146+
assert(&result == &x);
147+
assert(!x.__has_value());
148+
assert(!y.__has_value());
149+
}
150+
// check self-assignment in empty case
151+
{
152+
Box x = create_empty_box();
153+
Box& result = (x = x);
154+
155+
assert(&result == &x);
156+
assert(!x.__has_value());
157+
}
158+
}
159+
#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
160+
161+
int main(int, char**) {
162+
assert(test());
163+
static_assert(test());
164+
165+
#if !defined(TEST_HAS_NO_EXCEPTIONS)
166+
test_empty_state();
167+
#endif
168+
169+
return 0;
170+
}

0 commit comments

Comments
 (0)