WPILibC++  2019.1.1-beta-4-11-ga7f4e29
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
Signal.h
1 /*----------------------------------------------------------------------------*/
2 /* Copyright (c) 2018 FIRST. All Rights Reserved. */
3 /* Open Source Software - may be modified and shared by FRC teams. The code */
4 /* must be accompanied by the FIRST BSD license file in the root directory of */
5 /* the project. */
6 /*----------------------------------------------------------------------------*/
7 
8 /*
9 
10 Sigslot, a signal-slot library
11 
12 https://github.com/palacaze/sigslot
13 
14 MIT License
15 
16 Copyright (c) 2017 Pierre-Antoine Lacaze
17 
18 Permission is hereby granted, free of charge, to any person obtaining a copy
19 of this software and associated documentation files (the "Software"), to deal
20 in the Software without restriction, including without limitation the rights
21 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 copies of the Software, and to permit persons to whom the Software is
23 furnished to do so, subject to the following conditions:
24 
25 The above copyright notice and this permission notice shall be included in all
26 copies or substantial portions of the Software.
27 
28 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34 SOFTWARE.
35  */
36 #pragma once
37 #include <atomic>
38 #include <functional>
39 #include <memory>
40 #include <type_traits>
41 #include <utility>
42 
43 #include "wpi/mutex.h"
44 
45 namespace wpi {
46 
47 namespace sig {
48 
49 namespace trait {
50 
52 template <typename...> struct typelist {};
53 
60 template<typename T>
61 std::weak_ptr<T> to_weak(std::weak_ptr<T> w) {
62  return w;
63 }
64 
65 template<typename T>
66 std::weak_ptr<T> to_weak(std::shared_ptr<T> s) {
67  return s;
68 }
69 
70 // tools
71 namespace detail {
72 
73 template <class...>
74 struct voider { using type = void; };
75 
76 // void_t from c++17
77 template <class...T>
78 using void_t = typename detail::voider<T...>::type;
79 
80 
81 template <typename, typename, typename = void, typename = void>
82 struct is_callable : std::false_type {};
83 
84 template <typename F, typename P, typename... T>
85 struct is_callable<F, P, typelist<T...>,
86  void_t<decltype(((*std::declval<P>()).*std::declval<F>())(std::declval<T>()...))>>
87  : std::true_type {};
88 
89 template <typename F, typename... T>
90 struct is_callable<F, typelist<T...>,
91  void_t<decltype(std::declval<F>()(std::declval<T>()...))>>
92  : std::true_type {};
93 
94 
95 template <typename T, typename = void>
96 struct is_weak_ptr : std::false_type {};
97 
98 template <typename T>
99 struct is_weak_ptr<T, void_t<decltype(std::declval<T>().expired()),
100  decltype(std::declval<T>().lock()),
101  decltype(std::declval<T>().reset())>>
102  : std::true_type {};
103 
104 template <typename T, typename = void>
105 struct is_weak_ptr_compatible : std::false_type {};
106 
107 template <typename T>
108 struct is_weak_ptr_compatible<T, void_t<decltype(to_weak(std::declval<T>()))>>
109  : is_weak_ptr<decltype(to_weak(std::declval<T>()))> {};
110 
111 } // namespace detail
112 
114 template <typename P>
115 constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible<std::decay_t<P>>::value;
116 
118 template <typename L, typename... T>
119 constexpr bool is_callable_v = detail::is_callable<T..., L>::value;
120 
121 } // namespace trait
122 
123 
124 namespace detail {
125 
126 /* SlotState holds slot type independent state, to be used to interact with
127  * slots indirectly through connection and ScopedConnection objects.
128  */
129 class SlotState {
130 public:
131  constexpr SlotState() noexcept
132  : m_connected(true),
133  m_blocked(false) {}
134 
135  virtual ~SlotState() = default;
136 
137  bool connected() const noexcept { return m_connected; }
138  bool disconnect() noexcept { return m_connected.exchange(false); }
139 
140  bool blocked() const noexcept { return m_blocked.load(); }
141  void block() noexcept { m_blocked.store(true); }
142  void unblock() noexcept { m_blocked.store(false); }
143 
144 private:
145  std::atomic<bool> m_connected;
146  std::atomic<bool> m_blocked;
147 };
148 
149 } // namespace detail
150 
155 public:
156  ConnectionBlocker() = default;
157  ~ConnectionBlocker() noexcept { release(); }
158 
159  ConnectionBlocker(const ConnectionBlocker &) = delete;
160  ConnectionBlocker & operator=(const ConnectionBlocker &) = delete;
161 
162  ConnectionBlocker(ConnectionBlocker && o) noexcept
163  : m_state{std::move(o.m_state)}
164  {}
165 
166  ConnectionBlocker & operator=(ConnectionBlocker && o) noexcept {
167  release();
168  m_state.swap(o.m_state);
169  return *this;
170  }
171 
172 private:
173  friend class Connection;
174  ConnectionBlocker(std::weak_ptr<detail::SlotState> s) noexcept
175  : m_state{std::move(s)}
176  {
177  auto d = m_state.lock();
178  if (d) d->block();
179  }
180 
181  void release() noexcept {
182  auto d = m_state.lock();
183  if (d) d->unblock();
184  }
185 
186 private:
187  std::weak_ptr<detail::SlotState> m_state;
188 };
189 
190 
198 class Connection {
199 public:
200  Connection() = default;
201  virtual ~Connection() = default;
202 
203  Connection(const Connection &) noexcept = default;
204  Connection & operator=(const Connection &) noexcept = default;
205  Connection(Connection &&) noexcept = default;
206  Connection & operator=(Connection &&) noexcept = default;
207 
208  bool valid() const noexcept {
209  return !m_state.expired();
210  }
211 
212  bool connected() const noexcept {
213  const auto d = m_state.lock();
214  return d && d->connected();
215  }
216 
217  bool disconnect() noexcept {
218  auto d = m_state.lock();
219  return d && d->disconnect();
220  }
221 
222  bool blocked() const noexcept {
223  const auto d = m_state.lock();
224  return d && d->blocked();
225  }
226 
227  void block() noexcept {
228  auto d = m_state.lock();
229  if(d)
230  d->block();
231  }
232 
233  void unblock() noexcept {
234  auto d = m_state.lock();
235  if(d)
236  d->unblock();
237  }
238 
239  ConnectionBlocker blocker() const noexcept {
240  return ConnectionBlocker{m_state};
241  }
242 
243 protected:
244  template <typename, typename...> friend class SignalBase;
245  Connection(std::weak_ptr<detail::SlotState> s) noexcept
246  : m_state{std::move(s)}
247  {}
248 
249 protected:
250  std::weak_ptr<detail::SlotState> m_state;
251 };
252 
257 class ScopedConnection : public Connection {
258 public:
259  ScopedConnection() = default;
260  ~ScopedConnection() {
261  disconnect();
262  }
263 
264  ScopedConnection(const Connection &c) noexcept : Connection(c) {}
265  ScopedConnection(Connection &&c) noexcept : Connection(std::move(c)) {}
266 
267  ScopedConnection(const ScopedConnection &) noexcept = delete;
268  ScopedConnection & operator=(const ScopedConnection &) noexcept = delete;
269 
270  ScopedConnection(ScopedConnection && o) noexcept
271  : Connection{std::move(o.m_state)}
272  {}
273 
274  ScopedConnection & operator=(ScopedConnection && o) noexcept {
275  disconnect();
276  m_state.swap(o.m_state);
277  return *this;
278  }
279 
280 private:
281  template <typename, typename...> friend class SignalBase;
282  ScopedConnection(std::weak_ptr<detail::SlotState> s) noexcept
283  : Connection{std::move(s)}
284  {}
285 };
286 
287 namespace detail {
288 
289 template <typename...>
290 class SlotBase;
291 
292 template <typename... T>
293 using SlotPtr = std::shared_ptr<SlotBase<T...>>;
294 
295 /* A base class for slot objects. This base type only depends on slot argument
296  * types, it will be used as an element in an intrusive singly-linked list of
297  * slots, hence the public next member.
298  */
299 template <typename... Args>
300 class SlotBase : public SlotState {
301 public:
302  using base_types = trait::typelist<Args...>;
303 
304  virtual ~SlotBase() noexcept = default;
305 
306  // method effectively responsible for calling the "slot" function with
307  // supplied arguments whenever emission happens.
308  virtual void call_slot(Args...) = 0;
309 
310  template <typename... U>
311  void operator()(U && ...u) {
312  if (SlotState::connected() && !SlotState::blocked())
313  call_slot(std::forward<U>(u)...);
314  }
315 
316  SlotPtr<Args...> next;
317 };
318 
319 template <typename, typename...> class Slot {};
320 
321 /*
322  * A slot object holds state information, and a callable to to be called
323  * whenever the function call operator of its SlotBase base class is called.
324  */
325 template <typename Func, typename... Args>
326 class Slot<Func, trait::typelist<Args...>> : public SlotBase<Args...> {
327 public:
328  template <typename F>
329  constexpr Slot(F && f) : func{std::forward<F>(f)} {}
330 
331  virtual void call_slot(Args ...args) override {
332  func(args...);
333  }
334 
335 private:
336  std::decay_t<Func> func;
337 };
338 
339 /*
340  * Variation of slot that prepends a Connection object to the callable
341  */
342 template <typename Func, typename... Args>
343 class Slot<Func, trait::typelist<Connection&, Args...>> : public SlotBase<Args...> {
344 public:
345  template <typename F>
346  constexpr Slot(F && f) : func{std::forward<F>(f)} {}
347 
348  virtual void call_slot(Args ...args) override {
349  func(conn, args...);
350  }
351 
352  Connection conn;
353 
354 private:
355  std::decay_t<Func> func;
356 };
357 
358 /*
359  * A slot object holds state information, an object and a pointer over member
360  * function to be called whenever the function call operator of its SlotBase
361  * base class is called.
362  */
363 template <typename Pmf, typename Ptr, typename... Args>
364 class Slot<Pmf, Ptr, trait::typelist<Args...>> : public SlotBase<Args...> {
365 public:
366  template <typename F, typename P>
367  constexpr Slot(F && f, P && p)
368  : pmf{std::forward<F>(f)},
369  ptr{std::forward<P>(p)} {}
370 
371  virtual void call_slot(Args ...args) override {
372  ((*ptr).*pmf)(args...);
373  }
374 
375 private:
376  std::decay_t<Pmf> pmf;
377  std::decay_t<Ptr> ptr;
378 };
379 
380 /*
381  * Variation of slot that prepends a Connection object to the callable
382  */
383 template <typename Pmf, typename Ptr, typename... Args>
384 class Slot<Pmf, Ptr, trait::typelist<Connection&, Args...>> : public SlotBase<Args...> {
385 public:
386  template <typename F, typename P>
387  constexpr Slot(F && f, P && p)
388  : pmf{std::forward<F>(f)},
389  ptr{std::forward<P>(p)} {}
390 
391  virtual void call_slot(Args ...args) override {
392  ((*ptr).*pmf)(conn, args...);
393  }
394 
395  Connection conn;
396 
397 private:
398  std::decay_t<Pmf> pmf;
399  std::decay_t<Ptr> ptr;
400 };
401 
402 template <typename, typename, typename...> class SlotTracked {};
403 
404 /*
405  * An implementation of a slot that tracks the life of a supplied object
406  * through a weak pointer in order to automatically disconnect the slot
407  * on said object destruction.
408  */
409 template <typename Func, typename WeakPtr, typename... Args>
410 class SlotTracked<Func, WeakPtr, trait::typelist<Args...>> : public SlotBase<Args...> {
411 public:
412  template <typename F, typename P>
413  constexpr SlotTracked(F && f, P && p)
414  : func{std::forward<F>(f)},
415  ptr{std::forward<P>(p)}
416  {}
417 
418  virtual void call_slot(Args ...args) override {
419  if (! SlotState::connected())
420  return;
421  if (ptr.expired())
422  SlotState::disconnect();
423  else
424  func(args...);
425  }
426 
427 private:
428  std::decay_t<Func> func;
429  std::decay_t<WeakPtr> ptr;
430 };
431 
432 template <typename, typename, typename...> class SlotPmfTracked {};
433 
434 /*
435  * An implementation of a slot as a pointer over member function, that tracks
436  * the life of a supplied object through a weak pointer in order to automatically
437  * disconnect the slot on said object destruction.
438  */
439 template <typename Pmf, typename WeakPtr, typename... Args>
440 class SlotPmfTracked<Pmf, WeakPtr, trait::typelist<Args...>> : public SlotBase<Args...> {
441 public:
442  template <typename F, typename P>
443  constexpr SlotPmfTracked(F && f, P && p)
444  : pmf{std::forward<F>(f)},
445  ptr{std::forward<P>(p)}
446  {}
447 
448  virtual void call_slot(Args ...args) override {
449  if (! SlotState::connected())
450  return;
451  auto sp = ptr.lock();
452  if (!sp)
453  SlotState::disconnect();
454  else
455  ((*sp).*pmf)(args...);
456  }
457 
458 private:
459  std::decay_t<Pmf> pmf;
460  std::decay_t<WeakPtr> ptr;
461 };
462 
463 
464 // noop mutex for thread-unsafe use
465 struct NullMutex {
466  NullMutex() = default;
467  NullMutex(const NullMutex &) = delete;
468  NullMutex operator=(const NullMutex &) = delete;
469  NullMutex(NullMutex &&) = delete;
470  NullMutex operator=(NullMutex &&) = delete;
471 
472  bool try_lock() { return true; }
473  void lock() {}
474  void unlock() {}
475 };
476 
477 } // namespace detail
478 
479 
494 template <typename Lockable, typename... T>
495 class SignalBase {
496  using lock_type = std::unique_lock<Lockable>;
497  using SlotPtr = detail::SlotPtr<T...>;
498 
499  struct CallSlots {
500  SlotPtr m_slots;
501  SignalBase& m_base;
502 
503  CallSlots(SignalBase& base) : m_base(base) {}
504 
505  template <typename... A>
506  void operator()(A && ... a) {
507  SlotPtr *prev = nullptr;
508  SlotPtr *curr = m_slots ? &m_slots : nullptr;
509 
510  while (curr) {
511  // call non blocked, non connected slots
512  if ((*curr)->connected()) {
513  if (!m_base.m_block && !(*curr)->blocked())
514  (*curr)->operator()(a...);
515  prev = curr;
516  curr = (*curr)->next ? &((*curr)->next) : nullptr;
517  }
518  // remove slots marked as disconnected
519  else {
520  if (prev) {
521  (*prev)->next = (*curr)->next;
522  curr = (*prev)->next ? &((*prev)->next) : nullptr;
523  }
524  else
525  curr = (*curr)->next ? &((*curr)->next) : nullptr;
526  }
527  }
528  }
529  };
530 
531 public:
532  using arg_list = trait::typelist<T...>;
533  using ext_arg_list = trait::typelist<Connection&, T...>;
534 
535  SignalBase() noexcept : m_block(false) {}
536  ~SignalBase() {
537  disconnect_all();
538  }
539 
540  SignalBase(const SignalBase&) = delete;
541  SignalBase & operator=(const SignalBase&) = delete;
542 
543  SignalBase(SignalBase && o)
544  : m_block{o.m_block.load()}
545  {
546  lock_type lock(o.m_mutex);
547  std::swap(m_func, o.m_func);
548  }
549 
550  SignalBase & operator=(SignalBase && o) {
551  lock_type lock1(m_mutex, std::defer_lock);
552  lock_type lock2(o.m_mutex, std::defer_lock);
553  std::lock(lock1, lock2);
554 
555  std::swap(m_func, o.m_func);
556  m_block.store(o.m_block.exchange(m_block.load()));
557  return *this;
558  }
559 
572  template <typename... A>
573  void operator()(A && ... a) {
574  lock_type lock(m_mutex);
575  if (!m_block && m_func) m_func(std::forward<A>(a)...);
576  }
577 
588  template <typename Callable>
589  void connect(Callable && c) {
590  if (!m_func) {
591  m_func = std::forward<Callable>(c);
592  } else {
593  using slot_t = detail::Slot<Callable, arg_list>;
594  auto s = std::make_shared<slot_t>(std::forward<Callable>(c));
595  add_slot(s);
596  }
597  }
598 
609  template <typename Callable>
610  std::enable_if_t<trait::is_callable_v<arg_list, Callable>, Connection>
611  connect_connection(Callable && c) {
612  using slot_t = detail::Slot<Callable, arg_list>;
613  auto s = std::make_shared<slot_t>(std::forward<Callable>(c));
614  add_slot(s);
615  return Connection(s);
616  }
617 
627  template <typename Callable>
628  std::enable_if_t<trait::is_callable_v<ext_arg_list, Callable>, Connection>
629  connect_extended(Callable && c) {
631  auto s = std::make_shared<slot_t>(std::forward<Callable>(c));
632  s->conn = Connection(s);
633  add_slot(s);
634  return Connection(s);
635  }
636 
644  template <typename Pmf, typename Ptr>
645  std::enable_if_t<trait::is_callable_v<arg_list, Pmf, Ptr> &&
646  !trait::is_weak_ptr_compatible_v<Ptr>, Connection>
647  connect(Pmf && pmf, Ptr && ptr) {
648  using slot_t = detail::Slot<Pmf, Ptr, arg_list>;
649  auto s = std::make_shared<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(ptr));
650  add_slot(s);
651  return Connection(s);
652  }
653 
661  template <typename Pmf, typename Ptr>
662  std::enable_if_t<trait::is_callable_v<ext_arg_list, Pmf, Ptr> &&
663  !trait::is_weak_ptr_compatible_v<Ptr>, Connection>
664  connect_extended(Pmf && pmf, Ptr && ptr) {
666  auto s = std::make_shared<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(ptr));
667  s->conn = Connection(s);
668  add_slot(s);
669  return Connection(s);
670  }
671 
688  template <typename Pmf, typename Ptr>
689  std::enable_if_t<!trait::is_callable_v<arg_list, Pmf> &&
690  trait::is_weak_ptr_compatible_v<Ptr>, Connection>
691  connect(Pmf && pmf, Ptr && ptr) {
692  using trait::to_weak;
693  auto w = to_weak(std::forward<Ptr>(ptr));
695  auto s = std::make_shared<slot_t>(std::forward<Pmf>(pmf), w);
696  add_slot(s);
697  return Connection(s);
698  }
699 
716  template <typename Callable, typename Trackable>
717  std::enable_if_t<trait::is_callable_v<arg_list, Callable> &&
718  trait::is_weak_ptr_compatible_v<Trackable>, Connection>
719  connect(Callable && c, Trackable && ptr) {
720  using trait::to_weak;
721  auto w = to_weak(std::forward<Trackable>(ptr));
723  auto s = std::make_shared<slot_t>(std::forward<Callable>(c), w);
724  add_slot(s);
725  return Connection(s);
726  }
727 
732  template <typename... CallArgs>
733  ScopedConnection connect_scoped(CallArgs && ...args) {
734  return connect_connection(std::forward<CallArgs>(args)...);
735  }
736 
741  void disconnect_all() {
742  lock_type lock(m_mutex);
743  clear();
744  }
745 
750  void block() noexcept {
751  m_block.store(true);
752  }
753 
758  void unblock() noexcept {
759  m_block.store(false);
760  }
761 
765  bool blocked() const noexcept {
766  return m_block.load();
767  }
768 
769 private:
770  template <typename S>
771  void add_slot(S &s) {
772  lock_type lock(m_mutex);
773  if (!m_func) {
774  // nothing stored
775  m_func = CallSlots(*this);
776  auto slots = m_func.template target<CallSlots>();
777  s->next = slots->m_slots;
778  slots->m_slots = s;
779  } else if (auto call_slots = m_func.template target<CallSlots>()) {
780  // already CallSlots
781  s->next = call_slots->m_slots;
782  call_slots->m_slots = s;
783  } else {
784  // was normal std::function, need to move it into a call slot
785  using slot_t = detail::Slot<std::function<void(T...)>, arg_list>;
786  auto s2 = std::make_shared<slot_t>(
787  std::forward<std::function<void(T...)>>(m_func));
788  m_func = CallSlots(*this);
789  auto slots = m_func.template target<CallSlots>();
790  s2->next = slots->m_slots;
791  s->next = s2;
792  slots->m_slots = s;
793  }
794  }
795 
796  void clear() {
797  m_func = nullptr;
798  }
799 
800 private:
801  std::function<void(T...)> m_func;
802  Lockable m_mutex;
803  std::atomic<bool> m_block;
804 };
805 
811 template <typename... T>
812 using Signal = SignalBase<detail::NullMutex, T...>;
813 
823 template <typename... T>
824 using Signal_mt = SignalBase<mutex, T...>;
825 
831 template <typename... T>
832 using Signal_r = SignalBase<recursive_mutex, T...>;
833 
834 } // namespace sig
835 } // namespace wpi
ScopedConnection is a RAII version of Connection It disconnects the slot from the signal upon destruc...
Definition: Signal.h:257
Definition: Signal.h:74
Definition: Signal.h:402
Definition: Signal.h:465
std::enable_if_t< trait::is_callable_v< ext_arg_list, Pmf, Ptr > &&!trait::is_weak_ptr_compatible_v< Ptr >, Connection > connect_extended(Pmf &&pmf, Ptr &&ptr)
Overload of connect for pointer over member functions and.
Definition: Signal.h:664
std::enable_if_t<!trait::is_callable_v< arg_list, Pmf > &&trait::is_weak_ptr_compatible_v< Ptr >, Connection > connect(Pmf &&pmf, Ptr &&ptr)
Overload of connect for lifetime object tracking and automatic disconnection.
Definition: Signal.h:691
Definition: optional.h:885
std::enable_if_t< trait::is_callable_v< arg_list, Callable >, Connection > connect_connection(Callable &&c)
Connect a callable of compatible arguments, returning a Connection.
Definition: Signal.h:611
std::enable_if_t< trait::is_callable_v< ext_arg_list, Callable >, Connection > connect_extended(Callable &&c)
Connect a callable with an additional Connection argument.
Definition: Signal.h:629
WPILib C++ utilities (wpiutil) namespace.
Definition: SmallString.h:21
Definition: Signal.h:290
ConnectionBlocker is a RAII object that blocks a connection until destruction.
Definition: Signal.h:154
void block() noexcept
Blocks signal emission Safety: thread safe.
Definition: Signal.h:750
void unblock() noexcept
Unblocks signal emission Safety: thread safe.
Definition: Signal.h:758
Definition: Signal.h:319
void connect(Callable &&c)
Connect a callable of compatible arguments.
Definition: Signal.h:589
std::enable_if_t< trait::is_callable_v< arg_list, Callable > &&trait::is_weak_ptr_compatible_v< Trackable >, Connection > connect(Callable &&c, Trackable &&ptr)
Overload of connect for lifetime object tracking and automatic disconnection.
Definition: Signal.h:719
void disconnect_all()
Disconnects all the slots Safety: Thread safety depends on locking policy.
Definition: Signal.h:741
represent a list of types
Definition: Signal.h:52
A Connection object allows interaction with an ongoing slot connection.
Definition: Signal.h:198
ScopedConnection connect_scoped(CallArgs &&...args)
Creates a connection whose duration is tied to the return object Use the same semantics as connect...
Definition: Signal.h:733
std::enable_if_t< trait::is_callable_v< arg_list, Pmf, Ptr > &&!trait::is_weak_ptr_compatible_v< Ptr >, Connection > connect(Pmf &&pmf, Ptr &&ptr)
Overload of connect for pointers over member functions.
Definition: Signal.h:647
bool blocked() const noexcept
Tests blocking state of signal emission.
Definition: Signal.h:765
SignalBase is an implementation of the observer pattern, through the use of an emitting object and sl...
Definition: Signal.h:495
Definition: Signal.h:129
void operator()(A &&...a)
Emit a signal.
Definition: Signal.h:573
Definition: Signal.h:432