Coverage Report

Created: 2025-08-25 06:15

/src/immer/immer/set.hpp
Line
Count
Source
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/hamts/champ.hpp>
12
#include <immer/detail/hamts/champ_iterator.hpp>
13
#include <immer/memory_policy.hpp>
14
15
#include <functional>
16
17
namespace immer {
18
19
template <typename T,
20
          typename Hash,
21
          typename Equal,
22
          typename MemoryPolicy,
23
          detail::hamts::bits_t B>
24
class set_transient;
25
26
/*!
27
 * Immutable set representing an unordered bag of values.
28
 *
29
 * @tparam T    The type of the values to be stored in the container.
30
 * @tparam Hash The type of a function object capable of hashing
31
 *              values of type `T`.
32
 * @tparam Equal The type of a function object capable of comparing
33
 *              values of type `T`.
34
 * @tparam MemoryPolicy Memory management policy. See @ref
35
 *              memory_policy.
36
 *
37
 * @rst
38
 *
39
 * This container provides a good trade-off between cache locality,
40
 * membership checks, update performance and structural sharing.  It
41
 * does so by storing the data in contiguous chunks of :math:`2^{B}`
42
 * elements.  When storing big objects, the size of these contiguous
43
 * chunks can become too big, damaging performance.  If this is
44
 * measured to be problematic for a specific use-case, it can be
45
 * solved by using a `immer::box` to wrap the type `T`.
46
 *
47
 * **Example**
48
 *   .. literalinclude:: ../example/set/intro.cpp
49
 *      :language: c++
50
 *      :start-after: intro/start
51
 *      :end-before:  intro/end
52
 *
53
 * @endrst
54
 *
55
 */
56
template <typename T,
57
          typename Hash           = std::hash<T>,
58
          typename Equal          = std::equal_to<T>,
59
          typename MemoryPolicy   = default_memory_policy,
60
          detail::hamts::bits_t B = default_bits>
61
class set
62
{
63
    using impl_t = detail::hamts::champ<T, Hash, Equal, MemoryPolicy, B>;
64
65
    using move_t =
66
        std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
67
68
    struct project_value_ptr
69
    {
70
        const T* operator()(const T& v) const noexcept { return &v; }
71
    };
72
73
public:
74
    using value_type      = T;
75
    using size_type       = detail::hamts::size_t;
76
    using difference_type = std::ptrdiff_t;
77
    using hasher          = Hash;
78
    using key_equal       = Equal;
79
    using reference       = const T&;
80
    using const_reference = const T&;
81
82
    using iterator =
83
        detail::hamts::champ_iterator<T, Hash, Equal, MemoryPolicy, B>;
84
    using const_iterator = iterator;
85
86
    using transient_type = set_transient<T, Hash, Equal, MemoryPolicy, B>;
87
88
    using memory_policy_type = MemoryPolicy;
89
90
    /*!
91
     * Default constructor.  It creates a set of `size() == 0`.  It
92
     * does not allocate memory and its complexity is @f$ O(1) @f$.
93
     */
94
16.6k
    set() = default;
95
96
    /*!
97
     * Constructs a set containing the elements in `values`.
98
     */
99
    set(std::initializer_list<value_type> values)
100
        : impl_{impl_t::from_initializer_list(values)}
101
    {
102
    }
103
104
    /*!
105
     * Constructs a set containing the elements in the range
106
     * defined by the input iterator `first` and range sentinel `last`.
107
     */
108
    template <typename Iter,
109
              typename Sent,
110
              std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
111
                               bool> = true>
112
    set(Iter first, Sent last)
113
        : impl_{impl_t::from_range(first, last)}
114
    {
115
    }
116
117
    /*!
118
     * Returns an iterator pointing at the first element of the
119
     * collection. It does not allocate memory and its complexity is
120
     * @f$ O(1) @f$.
121
     */
122
7.36k
    IMMER_NODISCARD iterator begin() const { return {impl_}; }
123
124
    /*!
125
     * Returns an iterator pointing just after the last element of the
126
     * collection. It does not allocate and its complexity is @f$ O(1) @f$.
127
     */
128
    IMMER_NODISCARD iterator end() const
129
7.36k
    {
130
7.36k
        return {impl_, typename iterator::end_t{}};
131
7.36k
    }
132
133
    /*!
134
     * Returns the number of elements in the container.  It does
135
     * not allocate memory and its complexity is @f$ O(1) @f$.
136
     */
137
    IMMER_NODISCARD size_type size() const { return impl_.size; }
138
139
    /*!
140
     * Returns `true` if there are no elements in the container.  It
141
     * does not allocate memory and its complexity is @f$ O(1) @f$.
142
     */
143
    IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
144
145
    /*!
146
     * Returns `1` when `value` is contained in the set or `0`
147
     * otherwise. It won't allocate memory and its complexity is
148
     * *effectively* @f$ O(1) @f$.
149
     *
150
     * This overload participates in overload resolution only if
151
     * `Hash::is_transparent` is valid and denotes a type.
152
     */
153
    template <typename K,
154
              typename U = Hash,
155
              typename   = typename U::is_transparent>
156
    IMMER_NODISCARD size_type count(const K& value) const
157
    {
158
        return impl_.template get<detail::constantly<size_type, 1>,
159
                                  detail::constantly<size_type, 0>>(value);
160
    }
161
162
    /*!
163
     * Returns `1` when `value` is contained in the set or `0`
164
     * otherwise. It won't allocate memory and its complexity is
165
     * *effectively* @f$ O(1) @f$.
166
     */
167
    IMMER_NODISCARD size_type count(const T& value) const
168
178k
    {
169
178k
        return impl_.template get<detail::constantly<size_type, 1>,
170
178k
                                  detail::constantly<size_type, 0>>(value);
171
178k
    }
172
173
    /*!
174
     * Returns a pointer to the value if `value` is contained in the
175
     * set, or nullptr otherwise.
176
     * It does not allocate memory and its complexity is *effectively*
177
     * @f$ O(1) @f$.
178
     */
179
    IMMER_NODISCARD const T* find(const T& value) const
180
    {
181
        return impl_.template get<project_value_ptr,
182
                                  detail::constantly<const T*, nullptr>>(value);
183
    }
184
185
    /*!
186
     * Returns a pointer to the value if `value` is contained in the
187
     * set, or nullptr otherwise.
188
     * It does not allocate memory and its complexity is *effectively*
189
     * @f$ O(1) @f$.
190
     *
191
     * This overload participates in overload resolution only if
192
     * `Hash::is_transparent` is valid and denotes a type.
193
     */
194
    template <typename K,
195
              typename U = Hash,
196
              typename   = typename U::is_transparent>
197
    IMMER_NODISCARD const T* find(const K& value) const
198
    {
199
        return impl_.template get<project_value_ptr,
200
                                  detail::constantly<const T*, nullptr>>(value);
201
    }
202
203
    /*!
204
     * Returns whether the sets are equal.
205
     */
206
    IMMER_NODISCARD bool operator==(const set& other) const
207
    {
208
        return impl_.equals(other.impl_);
209
    }
210
    IMMER_NODISCARD bool operator!=(const set& other) const
211
    {
212
        return !(*this == other);
213
    }
214
215
    /*!
216
     * Returns a set containing `value`.  If the `value` is already in
217
     * the set, it returns the same set.  It may allocate memory and
218
     * its complexity is *effectively* @f$ O(1) @f$.
219
     */
220
    IMMER_NODISCARD set insert(T value) const&
221
150k
    {
222
150k
        return impl_.add(std::move(value));
223
150k
    }
224
    IMMER_NODISCARD decltype(auto) insert(T value) &&
225
23.6k
    {
226
23.6k
        return insert_move(move_t{}, std::move(value));
227
23.6k
    }
228
229
    /*!
230
     * Returns a set without `value`.  If the `value` is not in the
231
     * set it returns the same set.  It may allocate memory and its
232
     * complexity is *effectively* @f$ O(1) @f$.
233
     */
234
    IMMER_NODISCARD set erase(const T& value) const&
235
9.84k
    {
236
9.84k
        return impl_.sub(value);
237
9.84k
    }
238
    IMMER_NODISCARD decltype(auto) erase(const T& value) &&
239
9.74k
    {
240
9.74k
        return erase_move(move_t{}, value);
241
9.74k
    }
242
243
    /*!
244
     * Returns an @a transient form of this container, a
245
     * `immer::set_transient`.
246
     */
247
    IMMER_NODISCARD transient_type transient() const&
248
    {
249
        return transient_type{impl_};
250
    }
251
    IMMER_NODISCARD transient_type transient() &&
252
    {
253
        return transient_type{std::move(impl_)};
254
    }
255
256
    /*!
257
     * Returns a value that can be used as identity for the container.  If two
258
     * values have the same identity, they are guaranteed to be equal and to
259
     * contain the same objects.  However, two equal containers are not
260
     * guaranteed to have the same identity.
261
     */
262
    void* identity() const { return impl_.root; }
263
264
    // Semi-private
265
24.2k
    const impl_t& impl() const { return impl_; }
266
267
private:
268
    friend transient_type;
269
270
    set&& insert_move(std::true_type, value_type value)
271
23.6k
    {
272
23.6k
        impl_.add_mut({}, std::move(value));
273
23.6k
        return std::move(*this);
274
23.6k
    }
275
    set insert_move(std::false_type, value_type value)
276
    {
277
        return impl_.add(std::move(value));
278
    }
279
280
    set&& erase_move(std::true_type, const value_type& value)
281
9.74k
    {
282
9.74k
        impl_.sub_mut({}, value);
283
9.74k
        return std::move(*this);
284
9.74k
    }
285
    set erase_move(std::false_type, const value_type& value)
286
    {
287
        return impl_.sub(value);
288
    }
289
290
    // for immer::persist
291
public:
292
    set(impl_t impl)
293
160k
        : impl_(std::move(impl))
294
160k
    {
295
160k
    }
296
297
private:
298
    impl_t impl_ = impl_t::empty();
299
};
300
301
static_assert(std::is_nothrow_move_constructible<set<int>>::value,
302
              "set is not nothrow move constructible");
303
static_assert(std::is_nothrow_move_assignable<set<int>>::value,
304
              "set is not nothrow move assignable");
305
306
} // namespace immer