-
Notifications
You must be signed in to change notification settings - Fork 15
/
dyn.h
490 lines (470 loc) · 19.5 KB
/
dyn.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <concepts>
#include <type_traits>
#include "sus/boxed/boxed.h" // namespace docs.
#include "sus/macros/lifetimebound.h"
#include "sus/mem/forward.h"
#include "sus/mem/move.h"
namespace sus::boxed {
/// A concept for generalized type erasure of concepts, allowing use of a
/// concept-satisfying type `T` without knowing the concrete type `T`.
///
/// By providing a virtual type-erasure class that satisifies
/// [`DynConcept`]($sus::boxed::DynConcept) along
/// with a concept `C`, it allows the use of generic concept-satisfying objects
/// without the use of templates.
/// This means a function accepting a concept-satisying object as
/// a parameter can:
/// * Be written outside of the header.
/// * Be a virtual method.
/// * Be part of a non-header-only library API.
///
/// We can not specify a concept as a type parameter so there is a level of
/// indirection involved and the concept itself is not named.
///
/// To type-erase a concept `C`, there must be a virtual class `DynC` declared
/// that satisfies this [`DynConcept`]($sus::boxed::DynConcept) concept
/// for all concrete types `ConcreteT`
/// which satisfy the concept `C` being type-erased.
///
/// # Performing the type erasure
///
/// To type-erase a concept-satisfying object (a "concept object")
/// into the heap, use [`Box`]($sus::boxed::Box).
/// For example `Box<DynC>` would hold a type-erased
/// heap-allocated object that is known to satisfy the concept `C`. A
/// [`Box`]($sus::boxed::Box) should
/// always be used when storing the function object beyond the current
/// stack frame, such as in a class data member. It can also be done
/// for ease of working with type-erased concepts.
///
/// ```
/// // This function receives and uses a type-erased concept object.
/// void use_fn(sus::Box<sus::fn::DynFn<void(i32)>> b) { b(2); }
/// ```
///
/// A [`Box`]($sus::boxed::Box) holding a type-erased concept can be
/// constructed with the
/// [`from`]($sus::boxed::Box::from!dync) constructor method. It receives a
/// concept object as an input, and moves it to the heap.
/// Since this satisfies the [`From`]($sus::construct::From) concept, the
/// `Box<DynC>` can also be constructed with type deduction through
/// [`sus::into()`]($sus::construct::into).
///
/// ```
/// auto f = [](i32 i) { fmt::println("{}", i); };
/// // Converts the lambda, which satisfies the `Fn<void(i32)>` concept
/// // into a `Box<DynFn<void(i32)>>` for the function argument.
/// use_fn(sus::into(f));
/// ```
///
/// In performance-sensitive code, it can be necessary to avoid heap
/// allocations while working with type-erased concept objects, or to work with
/// a concept object without taking ownership of it. It is possible
/// to receive a type-erased concept object by reference instead of through a
/// [`Box`]($sus::boxed::Box).
///
/// ```
/// // This function receives and uses a type-erased concept object.
/// void use_fn_ref(const sus::fn::DynFn<void(i32)>& b) { b(2); }
/// ```
///
/// To get a type-erased reference from a concept object, pass it to
/// [`sus::dyn()`]($sus::boxed::dyn). The [`sus::dyn()`]($sus::boxed::dyn)
/// function constructs a type-erasure on the stack and automatically converts
/// to a reference to it.
///
/// ```
/// auto f = [](i32 i) { fmt::println("{}", i); };
/// // Erases the type of the lambda, constructing a type-erased reference to a
/// // `DynFn` to pass as the function argument.
/// use_fn_ref(sus::dyn<sus::fn::DynFn<void(i32)>>(f));
/// ```
///
/// # Type erasure of concepts in the Subspace library
///
/// Some concepts in the Subspace library come with a virtual type-erasure class
/// that satisfies [`DynConcept`]($sus::boxed::DynConcept) and can be
/// type-erased into `Box<DynC>` for the concept `C`:
/// * [`Error`]($sus::error::Error)
/// * [`Fn`]($sus::fn::Fn)
/// * [`FnMut`]($sus::fn::FnMut)
/// * [`FnOnce`]($sus::fn::FnOnce)
///
/// For some concepts in the Subspace library, `Box<DynC>` will also satisfy the
/// concept `C` itself, without having use the inner type. See
/// [Box implements some concepts for its inner type](
/// $sus::boxed::Box#box-implements-some-concepts-for-its-inner-type).
///
/// Since the actual type is erased, it can not be moved or copied. While it can
/// be constructed on the stack or the heap, any access to it other than its
/// initial declaration must be through a pointer or reference, similar to
/// [`Pin<T>`](https://doc.rust-lang.org/std/pin/struct.Pin.html) types in
/// Rust.
///
/// # Examples
///
/// ## Implementing concept type-erasure
///
/// Providing the mechanism to type erase objects that satisfy a concept named
/// `MyConcept` through a `DynMyConcept` class:
/// ```
/// // A concept which requires a single const-access method named `concept_fn`.
/// template <class T>
/// concept MyConcept = requires(const T& t) {
/// { t.concept_fn() } -> std::same_as<void>;
/// };
///
/// template <class T, class Store>
/// class DynMyConceptTyped;
///
/// class DynMyConcept {
/// sus_dyn_concept(MyConcept, DynMyConcept, DynMyConceptTyped);
///
/// public:
/// // Pure virtual concept API.
/// virtual void concept_fn() const = 0;
/// };
/// // Verifies that DynMyConcept also satisfies MyConcept, which is required.
/// static_assert(MyConcept<DynMyConcept>);
///
/// template <class T, class Store>
/// class DynMyConceptTyped final : public DynMyConcept {
/// sus_dyn_concept_typed(MyConcept, DynMyConcept, DynMyConceptTyped, v);
///
/// // Virtual concept API implementation.
/// void concept_fn() const override { return v.concept_fn(); };
/// };
///
/// // A type which satiesfies `MyConcept`.
/// struct MyConceptType {
/// void concept_fn() const {}
/// };
///
/// int main() {
/// // Verifies that DynMyConcept is functioning correctly, testing it against
/// // a type that satisfies MyConcept.
/// static_assert(sus::boxed::DynConcept<DynMyConcept, MyConceptType>);
///
/// auto b = [](Box<DynMyConcept> c) { c->concept_fn(); };
/// // `Box<DynMyConcept>` constructs from `MyConceptType`.
/// b(sus::into(MyConceptType()));
///
/// auto d = [](const DynMyConcept& c) { c.concept_fn(); };
/// // `MyConceptType` converts to `const MyConcept&` with `sus::dyn()`.
/// d(sus::dyn<const DynMyConcept>(MyConceptType()));
/// }
/// ```
///
/// An identical example to above, with a `DynMyConcept` class providing type
/// erasure for the `MyConcept` concept, however without the use of the helper
/// macros, showing all the required machinery:
/// ```
/// // A concept which requires a single const-access method named `concept_fn`.
/// template <class T>
/// concept MyConcept = requires(const T& t) {
/// { t.concept_fn() } -> std::same_as<void>;
/// };
///
/// template <class T, class Store>
/// class DynMyConceptTyped;
///
/// class DynMyConcept {
/// public:
/// // Pure virtual concept API.
/// virtual void concept_fn() const = 0;
/// template <class T>
///
/// static constexpr bool SatisfiesConcept = MyConcept<T>;
/// template <class T, class Store>
/// using DynTyped = DynMyConceptTyped<T, Store>;
///
/// DynMyConcept() = default;
/// virtual ~DynMyConcept() = default;
/// DynMyConcept(DynC&&) = delete;
/// DynMyConcept& operator=(DynMyConcept&&) = delete;
/// };
/// // Verifies that DynMyConcept also satisfies MyConcept, which is required.
/// static_assert(MyConcept<DynMyConcept>);
///
/// template <class T, class Store>
/// class DynMyConceptTyped final : public DynMyConcept {
/// public:
/// // Virtual concept API implementation.
/// void concept_fn() const override { return c_.concept_fn(); };
///
/// constexpr DynMyConceptTyped(Store&& c) : c_(::sus::forward<Store>(c)) {}
///
/// private:
/// Store c_;
/// };
///
/// // A type which satiesfies `MyConcept`.
/// struct MyConceptType {
/// void concept_fn() const {}
/// };
///
/// int main() {
/// // Verifies that DynMyConcept is functioning correctly, testing it against
/// // a type that satisfies MyConcept.
/// static_assert(sus::boxed::DynConcept<DynMyConcept, MyConceptType>);
///
/// auto b = [](Box<DynMyConcept> c) { c->concept_fn(); };
/// // `Box<DynMyConcept>` constructs from `MyConceptType`.
/// b(sus::into(MyConceptType()));
///
/// auto d = [](const DynMyConcept& c) { c.concept_fn(); };
/// // `MyConceptType` converts to `const MyConcept&` with `sus::dyn()`.
/// d(sus::dyn<const DynMyConcept>(MyConceptType()));
/// }
/// ```
///
/// ## Holding dyn() in a stack variable
///
/// When a function receives a type-erased `DynC&` by reference, it allows the
/// caller to avoid heap allocations should they wish. In the easy case, the
/// caller will simply call `sus::dyn()` directly in the function arguments to
/// construct the `DynC&` reference, which ensures it outlives the function
/// call.
///
/// In a more complicated scenario, the caller may wish to conditionally decide
/// to pass an Option<DynC&> with or without a reference, or to choose between
/// different references. It is not possible to return the result of
/// `sus::dyn()` without creating a dangling stack reference, which will be
/// caught by clang in most cases. This means in particular that lambdas such
/// as those passed to functions like [`Option::map`]($sus::option::Option::map)
/// can not be used to construct the `DynC&` reference.
///
/// In order to ensure the target of the `DynC&` reference outlives the function
/// it can be constructed as a stack variable before calling the function.
/// ```
/// std::srand(sus::cast<unsigned>(std::time(nullptr)));
///
/// auto x = [](sus::Option<sus::fn::DynFn<std::string()>&> fn) {
/// if (fn.is_some())
/// return sus::move(fn).unwrap()();
/// else
/// return std::string("tails");
/// };
///
/// auto heads = [] { return std::string("heads"); };
/// // Type-erased `Fn<std::string()>` that represents `heads`. Placed on the
/// // stack to outlive its use in the `Option` and the call to `x(cb)`.
/// auto dyn_heads = sus::dyn<sus::fn::DynFn<std::string()>>(heads);
/// // Conditionally holds a type-erased reference to `heads`. This requires a
/// // type-erasure that outlives the `cb` variable.
/// auto cb = [&]() -> sus::Option<sus::fn::DynFn<std::string()>&> {
/// if (std::rand() % 2) return sus::some(dyn_heads);
/// return sus::none();
/// }();
///
/// std::string s = x(cb);
///
/// fmt::println("{}", s); // Prints one of "heads" or "tails.
/// ```
/// It can greatly simplify correctness of code to use owned type-erased
/// concept objects through [`Box`]($sus::boxed::Box), such as
/// `Box<DynFn<std::string()>>` in the above example. Though references can be
/// useful, especially in simple or perf-critical code paths.
template <class DynC, class ConcreteT>
concept DynConcept = requires {
// The types are not qualified or references.
requires std::same_as<DynC, std::remove_cvref_t<DynC>>;
requires std::same_as<ConcreteT, std::remove_cvref_t<ConcreteT>>;
// The SatisfiesConcept bool tests against the concept.
{ DynC::template SatisfiesConcept<ConcreteT> } -> std::same_as<const bool&>;
// The `DynTyped` type alias names the typed subclass. The `DynTyped` class
// has two template parameters, the concrete type and the storage type (value
// or reference).
typename DynC::template DynTyped<ConcreteT, ConcreteT>;
// The type-erased `DynC` must also satisfy the concept, so it can be used
// in templated code still as well.
requires DynC::template SatisfiesConcept<DynC>;
// The typed class is a subclass of the type-erased `DynC` base class, and is
// final.
requires std::is_base_of_v<
DynC, typename DynC::template DynTyped<ConcreteT, ConcreteT>>;
requires std::is_final_v<
typename DynC::template DynTyped<ConcreteT, ConcreteT>>;
// The type-erased `DynC` can not be moved (which would slice the typed
// subclass off).
requires !std::is_move_constructible_v<DynC>;
requires !std::is_move_assignable_v<DynC>;
};
/// A type erasure of a type satisfying a concept, which can be used as a
/// reference without heap allocation or templates.
/// Returned from [`dyn`]($sus::boxed::dyn).
///
/// This type is similar to `Box<DynC>` for purposes of type erasure but does
/// not require heap allocation,
/// and it converts directly to a reference to the erased type.
///
/// Use [`dyn`]($sus::boxed::dyn) to convert to a `DynC` reference instead of
/// constructing this type directly.
///
/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of
/// concept-satisfying types.
template <class DynC, class ConcreteT>
class [[nodiscard]] Dyn {
public:
static_assert(std::same_as<const DynC, const std::remove_cvref_t<DynC>>,
"DynC can be const-qualified but not a reference");
static_assert(std::same_as<ConcreteT, std::remove_cvref_t<ConcreteT>>,
"ConcreteT can not be qualified or a reference");
static_assert(
DynC::template SatisfiesConcept<ConcreteT>,
"ConcreteT must satisfy the concept that DynC type-erases for.");
/// Construct a `Dyn<DynC, T>` from a mutable reference to `T`, which will
/// vend a mutable reference `DynC&`.
///
/// #[doc.overloads=mut]
Dyn(ConcreteT& concrete sus_lifetimebound)
requires(!std::is_const_v<DynC>)
: dyn_(concrete) {}
/// #[doc.overloads=mut]
Dyn(ConcreteT&& concrete sus_lifetimebound)
requires(!std::is_const_v<DynC>)
: dyn_(concrete) {}
/// Construct a `Dyn<const DynC, T>` from a reference to `T`, which will
/// vend a const reference `const DynC&`.
///
/// #[doc.overloads=const]
Dyn(const ConcreteT& concrete sus_lifetimebound)
requires(std::is_const_v<DynC>)
// This drops the const qualifier on `concrete` however we have a const
// qualifier on `DynC` (checked by the requires clause on this
// constructor) which prevents the `concrete` from being accessed in a
// non-const way through the `operator DynC&` overloads.
: dyn_(const_cast<ConcreteT&>(concrete)) {}
/// Converts the reference to `ConcreteT` into a `DynC` reference.
operator const DynC&() const& { return dyn_; }
operator DynC&() &
requires(!std::is_const_v<DynC>)
{
return dyn_;
}
operator DynC&() &&
requires(!std::is_const_v<DynC>)
{
return dyn_;
}
operator DynC&&() &&
requires(!std::is_const_v<DynC>)
{
return ::sus::move(dyn_);
}
/// `Dyn` can not be moved.
///
/// `Dyn` only exists as a temporary to convert a
/// concrete reference to a concept type to into a type-erased reference.
Dyn(Dyn&&) = delete;
/// `Dyn` can not be moved.
///
/// `Dyn` only exists as a temporary to convert a
/// concrete reference to a concept type to into a type-erased reference.
Dyn& operator=(Dyn&&) = delete;
private:
/// The typed subclass of `DynC` which holds the reference to `ConcreteT`.
typename DynC::template DynTyped<ConcreteT, ConcreteT&> dyn_;
};
/// Type erases a reference to a type `T&` which satisfies a concept `C`,
/// into a reference `DynC&` that also satisfies `C` but without templates.
///
/// Use `dyn<DynC>(x)` to convert a mutable reference to `x` into `DynC&` and
/// `dyn<const DynC>(x)` to convert a const or mutable reference to `x` into
/// `const Dyn&`.
///
/// Type erasure into `DynC` allows calling a method that receives a `DynC`
/// reference, such as `const DynC&`, without requiring a heap allocation into
/// a `Box<DynC>`.
///
/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of
/// concept-satisfying types.
template <class DynC, class ConcreteT>
requires(std::same_as<DynC, std::remove_cvref_t<DynC>> && //
std::same_as<ConcreteT, std::remove_cvref_t<ConcreteT>>)
constexpr Dyn<DynC, ConcreteT> dyn(ConcreteT& t sus_lifetimebound) noexcept {
return Dyn<DynC, ConcreteT>(t);
}
template <class DynC, class ConcreteT>
requires(std::same_as<DynC, std::remove_cvref_t<DynC>> && //
std::same_as<ConcreteT, std::remove_cvref_t<ConcreteT>> && //
std::is_rvalue_reference_v<ConcreteT &&>)
constexpr Dyn<DynC, ConcreteT> dyn(ConcreteT&& t sus_lifetimebound) noexcept {
return Dyn<DynC, ConcreteT>(t);
}
template <class DynC, class ConcreteT>
requires(std::same_as<const DynC, const std::remove_cvref_t<DynC>> &&
std::same_as<ConcreteT, std::remove_cvref_t<ConcreteT>> &&
std::is_const_v<DynC>)
constexpr Dyn<DynC, ConcreteT> dyn(
const ConcreteT& t sus_lifetimebound) noexcept {
return Dyn<DynC, ConcreteT>(t);
}
} // namespace sus::boxed
// Promote `dyn` to the top `sus` namespace.
namespace sus {
using sus::boxed::dyn;
}
/// Macro to help implement `DynC` for a concept `C`. The macro is placed in the
/// body of the `DynC` class.
///
/// Here `DynC` is used as a placeholder name to refer to the virtual class
/// that type-erases for the concept `C`. The type erasure class is typically
/// named to match the concept, with a "Dyn" prefix. The type-aware subclass
/// of the type erasure class is typically named to match the concept with a
/// "Dyn" prefix and a "Typed" suffix.
///
/// The `Concept` parameter is the concept `C` for which types are being
/// type-erased.
///
/// The `DynConcept` parameter is the name of the type-erasure
/// class `DynC` which the macro is written within, and which has a pure virtual
/// interface matching the concept's requirements.
///
/// The `DynConceptTyped`
/// parameter is the type-aware subclass of `DynC` which contains the
/// `sus_dyn_concept_typed` macro in its body, and the
/// implementation of the virtual interface that forwards calls through to the
/// concrete type.
///
/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of
/// concept-satisfying types, and [DynConcept examples](
/// $sus::boxed::DynConcept#examples) for examples of using the macro.
#define sus_dyn_concept(Concept, DynConcept, DynConceptTyped) \
public: \
template <class ConcreteT> \
static constexpr bool SatisfiesConcept = Concept<ConcreteT>; \
template <class ConcreteT, class Store> \
using DynTyped = DynConceptTyped<ConcreteT, Store>; \
\
DynConcept() = default; \
virtual ~DynConcept() = default; \
DynConcept(DynConcept&&) = delete; \
DynConcept& operator=(DynConcept&&) = delete
/// Macro to help implement `DynCTyped` for a concept `C`. The macro is placed
/// in the body of the `DynCTyped` class.
///
/// See the TODO: link [`sus_dyn_concept`] macro for more, and
/// [DynConcept examples]($sus::boxed::DynConcept#examples) for examples
/// of using the macro.
#define sus_dyn_concept_typed(Concept, DynConcept, DynConceptTyped, VarName) \
public: \
static_assert(Concept<T>); \
constexpr DynConceptTyped(Store&& c) : VarName(::sus::forward<Store>(c)) {} \
\
private: \
Store VarName;