libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
move_only_any.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include <algorithm>
10#include <array>
11#include <concepts>
12#include <cstddef>
13#include <initializer_list>
14#include <memory>
15#include <type_traits>
16#include <typeinfo>
17#include <utility>
18
19namespace tcspc::internal {
20
21struct bad_move_only_any_cast : std::bad_cast {
22 [[nodiscard]] auto what() const noexcept -> char const * override {
23 return "bad_move_only_any_cast";
24 }
25};
26
27// Define this outside the class so that assertions can be made.
28constexpr std::size_t move_only_any_sbo_size = 3 * sizeof(void *);
29
30// Like std::any, but move-only. Does not support types that throw during move
31// (will call std::terminate).
32class move_only_any {
33 struct polymorphic {
34 virtual ~polymorphic() = default;
35 virtual void move_construct_at(std::byte *storage) noexcept = 0;
36 [[nodiscard]] virtual auto const_ptr() const noexcept
37 -> void const * = 0;
38 [[nodiscard]] virtual auto ptr() noexcept -> void * = 0;
39 [[nodiscard]] virtual auto has_value() const noexcept -> bool = 0;
40 [[nodiscard]] virtual auto type() const noexcept
41 -> std::type_info const & = 0;
42 };
43
44 struct polymorphic_no_value : public polymorphic {
45 void move_construct_at(std::byte *storage) noexcept override {
46 new (storage) polymorphic_no_value();
47 }
48
49 [[nodiscard]] auto const_ptr() const noexcept
50 -> void const * override {
51 return nullptr;
52 }
53
54 [[nodiscard]] auto ptr() noexcept -> void * override {
55 return nullptr;
56 }
57
58 [[nodiscard]] auto has_value() const noexcept -> bool override {
59 return false;
60 }
61
62 [[nodiscard]] auto type() const noexcept
63 -> std::type_info const & override {
64 return typeid(void);
65 }
66 };
67
68 // Default: Store on heap
69 template <typename V> struct polymorphic_on_heap : public polymorphic {
70 std::unique_ptr<V> value;
71
72 explicit polymorphic_on_heap(std::unique_ptr<V> v)
73 : value(std::move(v)) {}
74
75 void move_construct_at(std::byte *storage) noexcept override {
76 new (storage) polymorphic_on_heap(std::move(value));
77 }
78
79 [[nodiscard]] auto const_ptr() const noexcept
80 -> void const * override {
81 return value.get();
82 }
83
84 [[nodiscard]] auto ptr() noexcept -> void * override {
85 return value.get();
86 }
87
88 [[nodiscard]] auto has_value() const noexcept -> bool override {
89 return true;
90 }
91
92 [[nodiscard]] auto type() const noexcept
93 -> std::type_info const & override {
94 return typeid(V);
95 }
96 };
97
98 // Small-buffer optimization.
99 template <typename V> struct polymorphic_sbo : public polymorphic {
100 V value;
101
102 template <typename... Args>
103 explicit polymorphic_sbo(Args &&...args)
104 : value(std::forward<Args>(args)...) {}
105
106 void move_construct_at(std::byte *storage) noexcept override {
107 new (storage) polymorphic_sbo(std::move(value));
108 }
109
110 [[nodiscard]] auto const_ptr() const noexcept
111 -> void const * override {
112 return &value;
113 }
114
115 [[nodiscard]] auto ptr() noexcept -> void * override { return &value; }
116
117 [[nodiscard]] auto has_value() const noexcept -> bool override {
118 return true;
119 }
120
121 [[nodiscard]] auto type() const noexcept
122 -> std::type_info const & override {
123 return typeid(V);
124 }
125 };
126
127 static constexpr std::size_t storage_size = std::max(
128 sizeof(polymorphic_on_heap<int>),
129 sizeof(
130 polymorphic_sbo<std::array<std::byte, move_only_any_sbo_size>>));
131
132 static constexpr std::size_t storage_align =
133 alignof(polymorphic_on_heap<int>);
134
135 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
136 alignas(storage_align) std::byte storage[storage_size];
137
138 // Explicit decay to stop clang-tidy from complaining.
139 [[nodiscard]] auto p_storage() noexcept -> std::byte * {
140 return static_cast<std::byte *>(storage);
141 }
142
143 [[nodiscard]] auto poly() noexcept -> polymorphic * {
144 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
145 return reinterpret_cast<polymorphic *>(storage);
146 }
147
148 [[nodiscard]] auto poly() const noexcept -> polymorphic const * {
149 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
150 return reinterpret_cast<polymorphic const *>(storage);
151 }
152
153 template <typename V> static constexpr auto uses_sbo() noexcept -> bool {
154 return sizeof(polymorphic_sbo<V>) <= storage_size &&
155 alignof(polymorphic_sbo<V>) <= storage_align;
156 }
157
158 template <typename U>
159 friend auto move_only_any_cast(move_only_any const &operand) -> U;
160
161 template <typename U>
162 friend auto move_only_any_cast(move_only_any &&operand) -> U;
163
164 template <typename V>
165 friend auto move_only_any_cast(move_only_any const *operand) noexcept
166 -> V const *;
167
168 template <typename V>
169 friend auto move_only_any_cast(move_only_any *operand) noexcept -> V *;
170
171 public:
172 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
173 move_only_any() { new (p_storage()) polymorphic_no_value(); }
174
175 ~move_only_any() { poly()->~polymorphic(); }
176
177 move_only_any(move_only_any const &other) = delete;
178 auto operator=(move_only_any const &rhs) = delete;
179
180 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
181 move_only_any(move_only_any &&other) noexcept {
182 other.poly()->move_construct_at(p_storage());
183 other.reset();
184 }
185
186 auto operator=(move_only_any &&rhs) noexcept -> move_only_any & {
187 poly()->~polymorphic();
188 rhs.poly()->move_construct_at(p_storage());
189 rhs.reset();
190 return *this;
191 }
192
193 template <typename V>
194 requires(not std::same_as<std::remove_reference_t<V>, move_only_any>)
195 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,google-explicit-constructor)
196 move_only_any(V &&value) {
197 using U = std::decay_t<V>;
198 if constexpr (uses_sbo<U>()) {
199 new (p_storage()) polymorphic_sbo<U>(std::forward<V>(value));
200 } else {
201 new (p_storage()) polymorphic_on_heap<U>(
202 std::make_unique<U>(std::forward<V>(value)));
203 }
204 }
205
206 template <typename V> auto operator=(V &&rhs) -> move_only_any & {
207 using U = std::decay_t<V>;
208 poly()->~polymorphic();
209 if constexpr (uses_sbo<U>()) {
210 new (p_storage()) polymorphic_sbo<U>(std::forward<V>(rhs));
211 } else {
212 new (p_storage()) polymorphic_on_heap<U>(
213 std::make_unique<U>(std::forward<V>(rhs)));
214 }
215 return *this;
216 }
217
218 template <typename V, typename... Args>
219 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
220 explicit move_only_any(std::in_place_type_t<V> /* tag */, Args &&...args) {
221 if constexpr (uses_sbo<V>()) {
222 new (p_storage()) polymorphic_sbo<V>(std::forward<Args>(args)...);
223 } else {
224 new (p_storage()) polymorphic_on_heap(
225 std::make_unique<V>(std::forward<Args>(args)...));
226 }
227 }
228
229 template <typename V, typename T, typename... Args>
230 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
231 explicit move_only_any(std::in_place_type_t<V> /* tag */,
232 std::initializer_list<T> il, Args &&...args) {
233 if constexpr (uses_sbo<V>()) {
234 new (p_storage())
235 polymorphic_sbo<V>(il, std::forward<Args>(args)...);
236 } else {
237 new (p_storage()) polymorphic_on_heap(
238 std::make_unique<V>(il, std::forward<Args>(args)...));
239 }
240 }
241
242 template <typename V, typename... Args>
243 auto emplace(Args &&...args) -> std::decay_t<V> & {
244 using U = std::decay_t<V>;
245 poly()->~polymorphic();
246 if constexpr (uses_sbo<U>()) {
247 new (p_storage()) polymorphic_sbo<U>(std::forward<Args>(args)...);
248 return *static_cast<U *>(
249 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
250 reinterpret_cast<polymorphic_sbo<U> *>(storage)->ptr());
251 } else {
252 new (p_storage()) polymorphic_on_heap<U>(
253 std::make_unique<U>(std::forward<Args>(args)...));
254 return *static_cast<U *>(
255 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
256 reinterpret_cast<polymorphic_on_heap<U> *>(storage)->ptr());
257 }
258 }
259
260 template <typename V, typename T, typename... Args>
261 auto emplace(std::initializer_list<T> il, Args &&...args)
262 -> std::decay_t<V> & {
263 using U = std::decay_t<V>;
264 poly()->~polymorphic();
265 if constexpr (uses_sbo<U>()) {
266 new (p_storage())
267 polymorphic_sbo<U>(il, std::forward<Args>(args)...);
268 return *static_cast<U *>(
269 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
270 reinterpret_cast<polymorphic_sbo<U> *>(storage)->ptr());
271 } else {
272 new (p_storage()) polymorphic_on_heap<U>(
273 std::make_unique<U>(il, std::forward<Args>(args)...));
274 return *static_cast<U *>(
275 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
276 reinterpret_cast<polymorphic_on_heap<U> *>(storage)->ptr());
277 }
278 }
279
280 void reset() noexcept {
281 poly()->~polymorphic();
282 new (p_storage()) polymorphic_no_value();
283 }
284
285 void swap(move_only_any &other) noexcept {
286 using std::swap;
287 swap(*this, other);
288 }
289
290 [[nodiscard]] auto has_value() const noexcept -> bool {
291 // We could use type() == typeid(void), but this can avoid value
292 // comparison of std::type_info (which, at least in theory, could
293 // involve a strcmp).
294 return poly()->has_value();
295 }
296
297 [[nodiscard]] auto type() const noexcept -> std::type_info const & {
298 return poly()->type();
299 }
300};
301
302// U must be V const &.
303template <typename U>
304auto move_only_any_cast(move_only_any const &operand) -> U {
305 using V = std::remove_cv_t<std::remove_reference_t<U>>;
306 auto const *ptr = move_only_any_cast<V>(&operand);
307 if (ptr == nullptr)
308 throw bad_move_only_any_cast();
309 return *ptr;
310}
311
312// U can be V & or V const &, but not V.
313template <typename U> auto move_only_any_cast(move_only_any &operand) -> U {
314 using V = std::remove_cv_t<std::remove_reference_t<U>>;
315 auto *ptr = move_only_any_cast<V>(&operand);
316 if (ptr == nullptr)
317 throw bad_move_only_any_cast();
318 return *ptr;
319}
320
321// U can be V const & or V, but not V &.
322// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
323template <typename U> auto move_only_any_cast(move_only_any &&operand) -> U {
324 using V = std::remove_cv_t<std::remove_reference_t<U>>;
325 auto *ptr = move_only_any_cast<V>(&operand);
326 if (ptr == nullptr)
327 throw bad_move_only_any_cast();
328 return std::move(*ptr);
329}
330
331// V must match type of contained value.
332template <typename V>
333auto move_only_any_cast(move_only_any const *operand) noexcept -> V const * {
334 static_assert(not std::is_void_v<V>);
335 if (operand->type() != typeid(V))
336 return nullptr;
337 return static_cast<V const *>(operand->poly()->const_ptr());
338}
339
340// V must match type of contained value.
341template <typename V>
342auto move_only_any_cast(move_only_any *operand) noexcept -> V * {
343 static_assert(not std::is_void_v<V>);
344 if (operand->type() != typeid(V))
345 return nullptr;
346 return static_cast<V *>(operand->poly()->ptr());
347}
348
349template <typename V, typename... Args>
350auto make_move_only_any(Args &&...args) -> move_only_any {
351 static_assert(not std::is_void_v<V>);
352 return move_only_any(std::in_place_type<V>, std::forward<Args>(args)...);
353}
354
355template <typename V, typename T, typename... Args>
356auto make_move_only_any(std::initializer_list<T> il, Args &&...args)
357 -> move_only_any {
358 static_assert(not std::is_void_v<V>);
359 return move_only_any(std::in_place_type<V>, il,
360 std::forward<Args>(args)...);
361}
362
363} // namespace tcspc::internal