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
14namespace immer {
15
16namespace detail {
17
18template <typename U, typename MP>
19struct gc_atom_impl;
20
21template <typename U, typename MP>
22struct 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 */
33template <typename T,
34 typename MemoryPolicy = default_memory_policy>
35class 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
54public:
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
161namespace std {
162
163template <typename T, typename MP>
164struct 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