qxLib
delegate.h
Go to the documentation of this file.
1 /**
2 
3  @file delegate.h
4  @author Khrapov
5  @date 7.01.2024
6  @copyright © Nick Khrapov, 2024. All right reserved.
7 
8 **/
9 #pragma once
10 
13 #include <qx/meta/concepts.h>
14 #include <qx/priority.h>
15 
16 #include <concepts>
17 #include <functional>
18 #include <map>
19 #include <memory>
20 #include <optional>
21 #include <variant>
22 
23 namespace qx
24 {
25 
26 template<class T>
27 concept delegate_pipe_c = std::default_initializable<T> && requires(T t) {
28  { t | t } -> std::convertible_to<T>;
29 };
30 
31 using delegate_token_type = time_ordered_priority_key;
32 
33 /**
34 
35  @class delegate
36  @brief Single or multicast delegate.
37 
38  @details For a singlecast version:
39  1. Create with qx::delegate<...>::create_singlecast(...);
40  2. Call delegate.execute(...).
41 
42  For a multicast version:
43  1. Default construct;
44  2. All various callbacks with delegate.add_xxx(...);
45  2. Call delegate.execute(...).
46 
47  If a delegate has only one function inside (i.e. singlecast delegate),
48  it's stored inplace without a container allocation.
49  If a function is small enough (token overload with max 2 bytes captured,
50  object + method pointer overload, weak overload with 1 byte max captured),
51  there will be no allocations at all.
52  The destruction callback overloads require 1 allocation for the alive marker (1 per delegate),
53  because they need to unsubscribe safely.
54 
55  @tparam signature_t - std::function-like signature of the delegate. For example: void(size_t, const qx::string&).
56  If the result type satisfies qx::delegate_pipe_c,
57  the result of each callable will be piped into the next one,
58  and the final result will be returned by execute().
59  Otherwise, the result of the last callable will be returned by execute(),
60  and the results of the previous callables will be ignored.
61  @author Khrapov
62  @date 4.07.2025
63 
64 **/
65 template<class signature_t>
66 class delegate;
67 
68 namespace details
69 {
70 
71 /**
72 
73  @class base_delegate
74  @brief Base delegate type
75  @tparam derived_t - CRTP derived class type
76  @tparam return_t - the exact type that all the passed callables and execute() should return
77  @tparam args_t - the exact type that all the passed callables and execute() should take
78  @author Khrapov
79  @date 17.02.2026
80 
81 **/
82 template<class derived_t, class return_t, class... args_t>
84 {
85  friend derived_t;
86 
87  base_delegate() noexcept = default;
88  base_delegate(base_delegate&& other) noexcept;
89  base_delegate(const base_delegate& other) noexcept = delete;
90 
91 public:
92  using function_type = std::function<return_t(args_t...)>;
93 
94 private:
95  using single_value_type = std::pair<time_ordered_priority_key, function_type>;
96  using container_type = std::map<time_ordered_priority_key, function_type>;
97  using variant_type = std::variant<single_value_type, container_type>;
98  using data_type = std::optional<variant_type>;
99 
100 public:
101  base_delegate& operator=(base_delegate&& other) noexcept;
102  base_delegate& operator=(const base_delegate& other) noexcept = delete;
103 
104  /**
105  @brief Create a singlecast delegate
106  @tparam creation_args_t - any arguments that can be used in add_weak or add_token
107  @param args - template parameter pack
108  @retval - created delegate
109  **/
110  template<class... creation_args_t>
111  static derived_t create_singlecast(creation_args_t... args) noexcept;
112 
113  /**
114  @brief Add a callable using a token with manual unsubscribing.
115  @warning If you capture `this` in the lambda passed and the object becomes invalid, it'll crash.
116  You must unsubscribe manually using remove()
117  Consider using add_destruction_callback or add_weak.
118  @tparam callable_t - any callable type: lambda, function, static method pointer, ect
119  @param callable - callable object
120  @param ePriority - callable priority. Callables will be called in order of priority,
121  from highest to lowest, and in the order they were added.
122  @retval - a token that can be used to remove this callable from the delegate
123  **/
124  template<callable_c<return_t, args_t...> callable_t>
125  [[maybe_unused]] delegate_token_type add_token(callable_t callable, priority ePriority = priority::normal) noexcept;
126 
127  /**
128  @brief Add a callable using a token with manual unsubscribing.
129  @warning If you capture `this` in the lambda passed and the object becomes invalid, it'll crash.
130  You must unsubscribe manually using remove()
131  Consider using add_destruction_callback or add_weak.
132  @tparam object_t - object type
133  @param object - object reference
134  @param pMethod - object's method pointer
135  @param ePriority - callable priority. Callables will be called in order of priority,
136  from highest to lowest, and in the order they were added.
137  @retval - a token that can be used to remove this callable from the delegate
138  **/
139  template<class object_t>
140  [[maybe_unused]] delegate_token_type add_token(
141  object_t& object,
142  return_t (object_t::*pMethod)(args_t...),
143  priority ePriority = priority::normal) noexcept;
144 
145  /**
146  @brief Add a callable that will be removed from the delegate when its destruction callback is destroyed
147  @tparam callable_t - any callable type: lambda, function, static method pointer, ect
148  @param callable - callable object
149  @param ePriority - callable priority. Callables will be called in order of priority,
150  from highest to lowest, and in the order they were added.
151  @retval - an object that removes this callable from the delegate on its destruction
152  **/
153  template<callable_c<return_t, args_t...> callable_t>
155  callable_t callable,
156  priority ePriority = priority::normal) noexcept;
157 
158  /**
159  @brief Add a callable that will be removed from the delegate when its destruction callback is destroyed
160  @tparam object_t - object type
161  @param object - object reference
162  @param pMethod - object's method pointer
163  @param ePriority - callable priority. Callables will be called in order of priority,
164  from highest to lowest, and in the order they were added.
165  @retval - an object that removes this callable from the delegate on its destruction
166  **/
167  template<class object_t>
169  object_t& object,
170  return_t (object_t::*pMethod)(args_t...),
171  priority ePriority = priority::normal) noexcept;
172 
173  /**
174  @brief Add a callable that will be executed only if the appropriate weak object is valid
175  @tparam object_t - object type
176  @tparam callable_t - any callable type: lambda, function, static method pointer, ect
177  @param pWeakObject - an object to track
178  @param callable - callable object
179  @param ePriority - callable priority. Callables will be called in order of priority,
180  from highest to lowest, and in the order they were added.
181  @retval - a token that can be used to remove this callable from the delegate
182  **/
183  template<class object_t, callable_c<return_t, args_t...> callable_t>
184  [[maybe_unused]] delegate_token_type add_weak(
185  std::weak_ptr<object_t> pWeakObject,
186  callable_t callable,
187  priority ePriority = priority::normal) noexcept;
188 
189  /**
190  @brief Add a callable that will be executed only if the appropriate weak object is valid
191  @tparam object_t - object type
192  @param pWeakObject - an object to track and to apply the method to
193  @param pMethod - object's method pointer
194  @param ePriority - callable priority. Callables will be called in order of priority,
195  from highest to lowest, and in the order they were added.
196  @retval - a token that can be used to remove this callable from the delegate
197  **/
198  template<class object_t>
199  [[maybe_unused]] delegate_token_type add_weak(
200  std::weak_ptr<object_t> pWeakObject,
201  return_t (object_t::*pMethod)(args_t...),
202  priority ePriority = priority::normal) noexcept;
203 
204  /**
205  @brief Remove a callable using its token
206  @param token - callable token
207  @retval - true is a callable was deleted
208  **/
209  bool remove(delegate_token_type token) noexcept;
210 
211  /**
212  @brief Clear all the callables in this delegate
213  **/
214  void clear() noexcept;
215 
216  /**
217  @brief Get the number of functions bound to this delegate
218  @retval - the number of functions bound to this delegate
219  **/
220  size_t size() const noexcept;
221 
222  /**
223  @brief Check if this delegate is empty
224  @retval - true if this delegate is empty
225  **/
226  bool empty() const noexcept;
227 
228 protected:
229  /**
230  @brief Execute all callables
231  @tparam invoke_single_t - a callable type that executes a delegate callable by moving args into it
232  @tparam invoke_multiple_t - a callable type that executes a delegate callable by copying args into it
233  @param invokeSingle - invoke_single_t object
234  @param invokeMultiple - invoke_multiple_t object
235  @retval - void or piping result of all delegate callables
236  **/
237  template<class invoke_single_t, class invoke_multiple_t>
238  return_t execute_internal(const invoke_single_t& invokeSingle, const invoke_multiple_t& invokeMultiple)
239  const noexcept;
240 
241 private:
242  /**
243  @brief Add a value using the destruction callback strategy
244  @param key - priority + time key
245  @param value - a function to add
246  @retval - a destruction callback
247  **/
249 
250  /**
251  @brief Add a delegate entity
252  @param key - priority + time key
253  @param function - a function to add
254  **/
255  void add_function(time_ordered_priority_key key, function_type function) noexcept;
256 
257  /**
258  @brief Add a value using the weak object strategy
259  @tparam object_t - weak object type
260  @tparam callable_t - a callable to execute if the weak is alive
261  @param key - priority + time key
262  @param pWeakObject - weak object to track
263  @param callable - callable_t object
264  **/
265  template<class object_t, callable_c<return_t, object_t*, args_t...> callable_t>
266  void add_weak(time_ordered_priority_key key, std::weak_ptr<object_t> pWeakObject, callable_t callable) noexcept;
267 
268 private:
269  data_type m_optFunctions;
270 
271  // in case a delegate was destroyed before a destruction callback
272  std::shared_ptr<bool> m_pDelegateAliveMarker;
273 };
274 
275 } // namespace details
276 
277 // @copydoc delegate
278 template<class return_t, class... args_t>
279  requires(sizeof...(args_t) > 0 && (!std::is_void_v<args_t> && ...))
280 class delegate<return_t(args_t...)> final
281  : public details::base_delegate<delegate<return_t(args_t...)>, return_t, args_t...>
282 {
283  using super_type = details::base_delegate<delegate, return_t, args_t...>;
284 
285 public:
286  /**
287  @brief Execute all the callables the delegate has
288  @param args - arguments to pass to all the callbacks
289  @retval - void or piping result of all delegate callables
290  **/
291  return_t execute(args_t... args) const noexcept;
292 };
293 
294 // @copydoc delegate
295 template<class return_t>
296 class delegate<return_t()> final : public details::base_delegate<delegate<return_t()>, return_t>
297 {
299 
300 public:
301  /**
302  @brief Execute all the callables the delegate has
303  @retval - void or piping result of all delegate callables
304  **/
305  return_t execute() const noexcept;
306 };
307 
308 } // namespace qx
309 
310 #include <qx/patterns/delegate.inl>
return_t execute() const noexcept
Execute all the callables the delegate has.
Single or multicast delegate.
Definition: delegate.h:66
Class for RAII: functor passed in constructor will be called in destructor.
Base delegate type.
Definition: delegate.h:84
delegate_token_type add_weak(std::weak_ptr< object_t > pWeakObject, callable_t callable, priority ePriority=priority::normal) noexcept
Add a callable that will be executed only if the appropriate weak object is valid.
Definition: delegate.inl:129
return_t execute_internal(const invoke_single_t &invokeSingle, const invoke_multiple_t &invokeMultiple) const noexcept
Execute all callables.
Definition: delegate.inl:237
void clear() noexcept
Clear all the callables in this delegate.
Definition: delegate.inl:198
destruction_callback add_destruction_callback(callable_t callable, priority ePriority=priority::normal) noexcept
Add a callable that will be removed from the delegate when its destruction callback is destroyed.
Definition: delegate.inl:103
bool empty() const noexcept
Check if this delegate is empty.
Definition: delegate.inl:230
size_t size() const noexcept
Get the number of functions bound to this delegate.
Definition: delegate.inl:204
static derived_t create_singlecast(creation_args_t... args) noexcept
Create a singlecast delegate.
Definition: delegate.inl:60
bool remove(delegate_token_type token) noexcept
Remove a callable using its token.
Definition: delegate.inl:166
delegate_token_type add_token(callable_t callable, priority ePriority=priority::normal) noexcept
Add a callable using a token with manual unsubscribing.
Definition: delegate.inl:75
A class that can be used as a key in ordered containers so that items are ordered in descending order...
Definition: priority.h:47
requires(same_variadic_args_v< args_t... >) const expr auto coalesce(args_t &&... args)
Coalesce function, C# a ?? b analogue.
Definition: coalesce.inl:57
priority
User may use the predefined values or the custom ones, for ex. "normal - 1", this type is supposed to...
Definition: priority.h:27
Static assert macros.