LeviLamina
Loading...
Searching...
No Matches
Dispatcher.h
1#pragma once
2
3#include <concepts>
4#include <utility>
5
6namespace ll::reflection {
7template <class Storage, class Listener, bool CallInit = false>
8 requires(std::default_initializable<Listener> && std::movable<Listener>)
9class Dispatcher {
10public:
11 using storage_type = Storage;
12 using listener_type = Listener;
13
14 Storage storage;
15 Listener listener;
16
17 void call() { listener.call(storage); }
18
19 template <class... Args>
20 Dispatcher(Args&&... args) : storage(std::forward<Args>(args)...),
21 listener() {
22 if constexpr (CallInit) {
23 call();
24 }
25 }
26
27 // A listener owns state - background tasks, hooks, event listeners - so handing a Dispatcher
28 // over has to carry that state along with the storage. Declaring these keeps a transfer a real
29 // move: without them it silently falls back to the variadic constructor above, which
30 // default-constructs a fresh listener and drops the old one, along with everything the old one
31 // owned. The Listener constraint is what makes the defaulted definitions well-formed, and it
32 // rejects at compile time the listeners that would otherwise lose their state this way.
33 Dispatcher(Dispatcher&&) = default;
34 Dispatcher& operator=(Dispatcher&&) = default;
35
36 Dispatcher& operator=(Storage const& other) {
37 storage = other;
38 call();
39 return *this;
40 }
41 Dispatcher& operator=(Storage&& other) {
42 storage = std::move(other);
43 call();
44 return *this;
45 }
46
47 operator Storage const&() const { return storage; }
48
49 operator Storage&() { return storage; }
50};
51} // namespace ll::reflection