1 | // |
2 | // immer: immutable data structures for C++ |
3 | // Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente |
4 | // |
5 | // This software is distributed under the Boost Software License, Version 1.0. |
6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt |
7 | // |
8 | |
9 | #pragma once |
10 | |
11 | #include <immer/detail/util.hpp> |
12 | #include <immer/memory_policy.hpp> |
13 | |
14 | namespace immer { |
15 | |
16 | namespace detail { |
17 | |
18 | template <typename U, typename MP> |
19 | struct gc_atom_impl; |
20 | |
21 | template <typename U, typename MP> |
22 | struct refcount_atom_impl; |
23 | |
24 | } // namespace detail |
25 | |
26 | /*! |
27 | * Immutable box for a single value of type `T`. |
28 | * |
29 | * The box is always copiable and movable. The `T` copy or move |
30 | * operations are never called. Since a box is immutable, copying or |
31 | * moving just copy the underlying pointers. |
32 | */ |
33 | template <typename T, |
34 | typename MemoryPolicy = default_memory_policy> |
35 | class box |
36 | { |
37 | friend struct detail::gc_atom_impl<T, MemoryPolicy>; |
38 | friend struct detail::refcount_atom_impl<T, MemoryPolicy>; |
39 | |
40 | struct holder : MemoryPolicy::refcount |
41 | { |
42 | T value; |
43 | |
44 | template <typename... Args> |
45 | holder(Args&&... args) : value{std::forward<Args>(args)...} {} |
46 | }; |
47 | |
48 | using heap = typename MemoryPolicy::heap::type; |
49 | |
50 | holder* impl_ = nullptr; |
51 | |
52 | box(holder* impl) : impl_{impl} {} |
53 | |
54 | public: |
55 | using value_type = T; |
56 | using memory_policy = MemoryPolicy; |
57 | |
58 | /*! |
59 | * Constructs a box holding `T{}`. |
60 | */ |
61 | box() : impl_{detail::make<heap, holder>()} {} |
62 | |
63 | /*! |
64 | * Constructs a box holding `T{arg}` |
65 | */ |
66 | template <typename Arg, |
67 | typename Enable=std::enable_if_t< |
68 | !std::is_same<box, std::decay_t<Arg>>::value && |
69 | std::is_constructible<T, Arg>::value>> |
70 | box(Arg&& arg) |
71 | : impl_{detail::make<heap, holder>(std::forward<Arg>(arg))} {} |
72 | |
73 | /*! |
74 | * Constructs a box holding `T{arg1, arg2, args...}` |
75 | */ |
76 | template <typename Arg1, typename Arg2, typename... Args> |
77 | box(Arg1&& arg1, Arg2&& arg2, Args&& ...args) |
78 | : impl_{detail::make<heap, holder>( |
79 | std::forward<Arg1>(arg1), |
80 | std::forward<Arg2>(arg2), |
81 | std::forward<Args>(args)...)} |
82 | {} |
83 | |
84 | friend void swap(box& a, box& b) |
85 | { using std::swap; swap(a.impl_, b.impl_); } |
86 | |
87 | box(box&& other) { swap(*this, other); } |
88 | box(const box& other) : impl_(other.impl_) { impl_->inc(); } |
89 | box& operator=(box&& other) { swap(*this, other); return *this; } |
90 | box& operator=(const box& other) |
91 | { |
92 | auto aux = other; |
93 | swap(*this, aux); |
94 | return *this; |
95 | } |
96 | ~box() |
97 | { |
98 | if (impl_ && impl_->dec()) { |
99 | impl_->~holder(); |
100 | heap::deallocate(sizeof(holder), impl_); |
101 | } |
102 | } |
103 | |
104 | /*! Query the current value. */ |
105 | IMMER_NODISCARD const T& get() const { return impl_->value; } |
106 | |
107 | /*! Conversion to the boxed type. */ |
108 | operator const T&() const { return get(); } |
109 | |
110 | /*! Access via dereference */ |
111 | const T& operator* () const { return get(); } |
112 | |
113 | /*! Access via pointer member access */ |
114 | const T* operator-> () const { return &get(); } |
115 | |
116 | /*! Comparison. */ |
117 | IMMER_NODISCARD bool operator==(detail::exact_t<const box&> other) const |
118 | { return impl_ == other.value.impl_ || get() == other.value.get(); } |
119 | // Note that the `exact_t` disambiguates comparisons against `T{}` |
120 | // directly. In that case we want to use `operator T&` and |
121 | // compare directly. We definitely never want to convert a value |
122 | // to a box (which causes an allocation) just to compare it. |
123 | IMMER_NODISCARD bool operator!=(detail::exact_t<const box&> other) const |
124 | { return !(*this == other.value); } |
125 | IMMER_NODISCARD bool operator<(detail::exact_t<const box&> other) const |
126 | { return get() < other.value.get(); } |
127 | |
128 | /*! |
129 | * Returns a new box built by applying the `fn` to the underlying |
130 | * value. |
131 | * |
132 | * @rst |
133 | * |
134 | * **Example** |
135 | * .. literalinclude:: ../example/box/box.cpp |
136 | * :language: c++ |
137 | * :dedent: 8 |
138 | * :start-after: update/start |
139 | * :end-before: update/end |
140 | * |
141 | * @endrst |
142 | */ |
143 | template <typename Fn> |
144 | IMMER_NODISCARD box update(Fn&& fn) const& |
145 | { |
146 | return std::forward<Fn>(fn)(get()); |
147 | } |
148 | template <typename Fn> |
149 | IMMER_NODISCARD box&& update(Fn&& fn) && |
150 | { |
151 | if (impl_->unique()) |
152 | impl_->value = std::forward<Fn>(fn)(std::move(impl_->value)); |
153 | else |
154 | *this = std::forward<Fn>(fn)(impl_->value); |
155 | return std::move(*this); |
156 | } |
157 | }; |
158 | |
159 | } // namespace immer |
160 | |
161 | namespace std { |
162 | |
163 | template <typename T, typename MP> |
164 | struct hash<immer::box<T, MP>> |
165 | { |
166 | std::size_t operator() (const immer::box<T, MP>& x) const |
167 | { |
168 | return std::hash<T>{}(*x); |
169 | } |
170 | }; |
171 | |
172 | } // namespace std |
173 | |