RAJA
RAJA provides a collection of platform portability abstractions for C++ HPC applications.
Dispatcher.hpp
Go to the documentation of this file.
1 
11 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
12 // Copyright (c) Lawrence Livermore National Security, LLC and other
13 // RAJA Project Developers. See top-level LICENSE and COPYRIGHT
14 // files for dates and other details. No copyright assignment is required
15 // to contribute to RAJA.
16 //
17 // SPDX-License-Identifier: (BSD-3-Clause)
18 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
19 
20 #ifndef RAJA_PATTERN_WORKGROUP_Dispatcher_HPP
21 #define RAJA_PATTERN_WORKGROUP_Dispatcher_HPP
22 
23 
24 #include "RAJA/config.hpp"
25 
27 
28 #include "camp/number.hpp"
29 #include "camp/list.hpp"
30 #include "camp/helpers.hpp"
31 
32 #include <utility>
33 
34 namespace RAJA
35 {
36 
37 namespace detail
38 {
39 
40 template<typename>
42 {
43  void* ptr;
45 
46  // implicit constructor from void*
48 };
49 
50 template<typename>
52 {
53  const void* ptr;
55 
56  // implicit constructor from const void*
58 };
59 
60 constexpr bool dispatcher_use_host_invoke(Platform platform)
61 {
62  return !(platform == Platform::cuda || platform == Platform::hip);
63 }
64 
65 // Transforms one dispatch policy into another by creating a dispatch policy
66 // of holder_type objects. See usage in WorkRunner for more explanation.
67 template<typename dispatch_policy, typename holder_type>
70 template<typename dispatch_policy, typename holder_type>
73 
81 template<Platform platform,
82  typename dispatch_policy,
83  typename DispatcherID,
84  typename... CallArgs>
85 struct Dispatcher;
86 
87 template<typename holder_type>
89  holder_type>
90 {
92 };
93 
103 template<Platform platform, typename DispatcherID, typename... CallArgs>
104 struct Dispatcher<platform,
106  DispatcherID,
107  CallArgs...>
108 {
109  static constexpr bool use_host_invoke = dispatcher_use_host_invoke(platform);
113 
118  template<typename T>
120  void_ptr_wrapper src)
121  {
122  T* dest_as_T = static_cast<T*>(dest.ptr);
123  T* src_as_T = static_cast<T*>(src.ptr);
124  new (dest_as_T) T(std::move(*src_as_T));
125  (*src_as_T).~T();
126  }
127 
131  template<typename T>
132  static void s_host_invoke(void_cptr_wrapper obj, CallArgs... args)
133  {
134  const T* obj_as_T = static_cast<const T*>(obj.ptr);
135  (*obj_as_T)(std::forward<CallArgs>(args)...);
136  }
137 
139  template<typename T>
141  CallArgs... args)
142  {
143  const T* obj_as_T = static_cast<const T*>(obj.ptr);
144  (*obj_as_T)(std::forward<CallArgs>(args)...);
145  }
146 
150  template<typename T>
151  static void s_destroy(void_ptr_wrapper obj)
152  {
153  T* obj_as_T = static_cast<T*>(obj.ptr);
154  (*obj_as_T).~T();
155  }
156 
157  using mover_type = void (*)(void_ptr_wrapper /*dest*/,
158  void_ptr_wrapper /*src*/);
159  using invoker_type = void (*)(void_cptr_wrapper /*obj*/,
160  CallArgs... /*args*/);
161  using destroyer_type = void (*)(void_ptr_wrapper /*obj*/);
162 
163  // This can't be a cuda device lambda due to compiler limitations
164  template<typename T>
165  struct DeviceInvokerFactory
166  {
168 
170  {
171 #if defined(RAJA_ENABLE_HIP) && !defined(RAJA_ENABLE_HIP_INDIRECT_FUNCTION_CALL)
172  return nullptr;
173 #else
174  return &s_device_invoke<T>;
175 #endif
176  }
177  };
178 
182  template<typename T,
183  bool uhi = use_host_invoke,
184  std::enable_if_t<uhi>* = nullptr>
185  static inline Dispatcher makeDispatcher()
186  {
187  return {mover_type {&s_move_construct_destroy<T>},
188  invoker_type {&s_host_invoke<T>}, destroyer_type {&s_destroy<T>},
189  sizeof(T)};
190  }
191 
202  template<typename T,
203  typename CreateOnDevice,
204  bool uhi = use_host_invoke,
205  std::enable_if_t<!uhi>* = nullptr>
206  static inline Dispatcher makeDispatcher(CreateOnDevice&& createOnDevice)
207  {
208  return {mover_type {&s_move_construct_destroy<T>},
209  invoker_type {std::forward<CreateOnDevice>(createOnDevice)(
210  DeviceInvokerFactory<T> {})},
211  destroyer_type {&s_destroy<T>}, sizeof(T)};
212  }
213 
217  size_t size;
218 };
219 
220 template<typename holder_type>
222  holder_type>
223 {
225 };
226 
236 template<Platform platform, typename DispatcherID, typename... CallArgs>
237 struct Dispatcher<platform,
239  DispatcherID,
240  CallArgs...>
241 {
242  static constexpr bool use_host_invoke = dispatcher_use_host_invoke(platform);
246 
247  struct impl_base
248  {
249  virtual void move_destroy(void_ptr_wrapper dest,
250  void_ptr_wrapper src) const = 0;
251  virtual void destroy(void_ptr_wrapper obj) const = 0;
252  };
253 
254  struct host_impl_base
255  {
256  virtual void invoke(void_cptr_wrapper obj, CallArgs... args) const = 0;
257  };
258 
259  struct device_impl_base
260  {
262  CallArgs... args) const = 0;
263  };
264 
265  template<typename T>
266  struct base_impl_type : impl_base
267  {
272  virtual void move_destroy(void_ptr_wrapper dest,
273  void_ptr_wrapper src) const override
274  {
275  T* dest_as_T = static_cast<T*>(dest.ptr);
276  T* src_as_T = static_cast<T*>(src.ptr);
277  new (dest_as_T) T(std::move(*src_as_T));
278  (*src_as_T).~T();
279  }
280 
284  virtual void destroy(void_ptr_wrapper obj) const override
285  {
286  T* obj_as_T = static_cast<T*>(obj.ptr);
287  (*obj_as_T).~T();
288  }
289  };
290 
291  template<typename T>
292  struct host_impl_type : host_impl_base
293  {
297  virtual void invoke(void_cptr_wrapper obj, CallArgs... args) const override
298  {
299  const T* obj_as_T = static_cast<const T*>(obj.ptr);
300  (*obj_as_T)(std::forward<CallArgs>(args)...);
301  }
302  };
303 
304  template<typename T>
305  struct device_impl_type : device_impl_base
306  {
311  CallArgs... args) const override
312  {
313  const T* obj_as_T = static_cast<const T*>(obj.ptr);
314  (*obj_as_T)(std::forward<CallArgs>(args)...);
315  }
316  };
317 
318  struct mover_type
319  {
320  impl_base* m_impl;
321 
323  {
324  m_impl->move_destroy(dest, src);
325  }
326  };
327 
328  struct host_invoker_type
329  {
330  host_impl_base* m_impl;
331 
332  void operator()(void_cptr_wrapper obj, CallArgs... args) const
333  {
334  m_impl->invoke(obj, std::forward<CallArgs>(args)...);
335  }
336  };
337 
339  struct device_invoker_type
340  {
341  device_impl_base* m_impl;
342 
343  RAJA_DEVICE void operator()(void_cptr_wrapper obj, CallArgs... args) const
344  {
345  m_impl->invoke(obj, std::forward<CallArgs>(args)...);
346  }
347  };
348 
349  using invoker_type = std::
350  conditional_t<use_host_invoke, host_invoker_type, device_invoker_type>;
351 
352  struct destroyer_type
353  {
354  impl_base* m_impl;
355 
356  void operator()(void_ptr_wrapper obj) const { m_impl->destroy(obj); }
357  };
358 
359  // This can't be a cuda device lambda due to compiler limitations
360  template<typename T>
361  struct DeviceImplTypeFactory
362  {
363  using value_type = device_impl_type<T>*;
364 
366  {
367 #if defined(RAJA_ENABLE_HIP) && !defined(RAJA_ENABLE_HIP_INDIRECT_FUNCTION_CALL)
368  return nullptr;
369 #else
370  static device_impl_type<T> s_device_impl;
371  return &s_device_impl;
372 #endif
373  }
374  };
375 
379  template<typename T,
380  bool uhi = use_host_invoke,
381  std::enable_if_t<uhi>* = nullptr>
382  static inline Dispatcher makeDispatcher()
383  {
384  static base_impl_type<T> s_base_impl;
385  static host_impl_type<T> s_host_impl;
386  return {mover_type {&s_base_impl}, host_invoker_type {&s_host_impl},
387  destroyer_type {&s_base_impl}, sizeof(T)};
388  }
389 
400  template<typename T,
401  typename CreateOnDevice,
402  bool uhi = use_host_invoke,
403  std::enable_if_t<!uhi>* = nullptr>
404  static inline Dispatcher makeDispatcher(CreateOnDevice&& createOnDevice)
405  {
406  static base_impl_type<T> s_base_impl;
407  static device_impl_type<T>* s_device_impl_ptr {std::forward<CreateOnDevice>(
408  createOnDevice)(DeviceImplTypeFactory<T> {})};
409  return {mover_type {&s_base_impl}, device_invoker_type {s_device_impl_ptr},
410  destroyer_type {&s_base_impl}, sizeof(T)};
411  }
412 
415  destroyer_type destroy;
416  size_t size;
417 };
418 
419 // direct_dispatch expects a list of types
420 template<typename... Ts, typename holder_type>
422 {
423  using type =
425 };
426 
431 template<Platform platform, typename DispatcherID, typename... CallArgs>
432 struct Dispatcher<platform,
434  DispatcherID,
435  CallArgs...>
436 {
437  static constexpr bool use_host_invoke = dispatcher_use_host_invoke(platform);
441 
446  struct mover_type
447  {
449  };
450 
454  struct host_invoker_type
455  {
456  void operator()(void_cptr_wrapper, CallArgs...) const {}
457  };
458 
459  struct device_invoker_type
460  {
461  RAJA_DEVICE void operator()(void_cptr_wrapper, CallArgs...) const {}
462  };
463 
464  using invoker_type = std::
465  conditional_t<use_host_invoke, host_invoker_type, device_invoker_type>;
466 
470  struct destroyer_type
471  {
473  };
474 
478  template<typename T,
479  bool uhi = use_host_invoke,
480  std::enable_if_t<uhi>* = nullptr>
481  static inline Dispatcher makeDispatcher()
482  {
483  return {mover_type {}, host_invoker_type {}, destroyer_type {}, sizeof(T)};
484  }
485 
492  template<typename T,
493  typename CreateOnDevice,
494  bool uhi = use_host_invoke,
495  std::enable_if_t<!uhi>* = nullptr>
496  static inline Dispatcher makeDispatcher(CreateOnDevice&&)
497  {
498  return {mover_type {}, device_invoker_type {}, destroyer_type {},
499  sizeof(T)};
500  }
501 
504  destroyer_type destroy;
505  size_t size;
506 };
507 
512 template<Platform platform,
513  typename T,
514  typename DispatcherID,
515  typename... CallArgs>
516 struct Dispatcher<platform,
518  DispatcherID,
519  CallArgs...>
520 {
521  static constexpr bool use_host_invoke = dispatcher_use_host_invoke(platform);
525 
530  struct mover_type
531  {
533  {
534  T* dest_as_T = static_cast<T*>(dest.ptr);
535  T* src_as_T = static_cast<T*>(src.ptr);
536  new (dest_as_T) T(std::move(*src_as_T));
537  (*src_as_T).~T();
538  }
539  };
540 
544  struct host_invoker_type
545  {
546  void operator()(void_cptr_wrapper obj, CallArgs... args) const
547  {
548  const T* obj_as_T = static_cast<const T*>(obj.ptr);
549  (*obj_as_T)(std::forward<CallArgs>(args)...);
550  }
551  };
552 
553  struct device_invoker_type
554  {
555  RAJA_DEVICE void operator()(void_cptr_wrapper obj, CallArgs... args) const
556  {
557  const T* obj_as_T = static_cast<const T*>(obj.ptr);
558  (*obj_as_T)(std::forward<CallArgs>(args)...);
559  }
560  };
561 
562  using invoker_type = std::
563  conditional_t<use_host_invoke, host_invoker_type, device_invoker_type>;
564 
568  struct destroyer_type
569  {
570  void operator()(void_ptr_wrapper obj) const
571  {
572  T* obj_as_T = static_cast<T*>(obj.ptr);
573  (*obj_as_T).~T();
574  }
575  };
576 
580  template<typename U,
581  bool uhi = use_host_invoke,
582  std::enable_if_t<uhi>* = nullptr>
583  static inline Dispatcher makeDispatcher()
584  {
585  static_assert(std::is_same<T, U>::value,
586  "U must be in direct_dispatch types");
587  return {mover_type {}, host_invoker_type {}, destroyer_type {}, sizeof(T)};
588  }
589 
596  template<typename U,
597  typename CreateOnDevice,
598  bool uhi = use_host_invoke,
599  std::enable_if_t<!uhi>* = nullptr>
600  static inline Dispatcher makeDispatcher(CreateOnDevice&&)
601  {
602  static_assert(std::is_same<T, U>::value,
603  "U must be in direct_dispatch types");
604  return {mover_type {}, device_invoker_type {}, destroyer_type {},
605  sizeof(T)};
606  }
607 
610  destroyer_type destroy;
611  size_t size;
612 };
613 
618 template<typename T0,
619  typename T1,
620  typename... TNs,
621  Platform platform,
622  typename DispatcherID,
623  typename... CallArgs>
624 struct Dispatcher<platform,
625  ::RAJA::direct_dispatch<T0, T1, TNs...>,
626  DispatcherID,
627  CallArgs...>
628 {
629  static constexpr bool use_host_invoke = dispatcher_use_host_invoke(platform);
630  using dispatch_policy = ::RAJA::direct_dispatch<T0, T1, TNs...>;
633 
634  using id_type = int;
635  using callable_indices = camp::make_int_seq_t<id_type, 2 + sizeof...(TNs)>;
636  using callable_types = camp::list<T0, T1, TNs...>;
637 
642  struct mover_type
643  {
645 
647  {
648  impl_helper(callable_indices {}, callable_types {}, dest, src);
649  }
650 
651  private:
652  template<int... id_types, typename... Ts>
653  void impl_helper(camp::int_seq<int, id_types...>,
654  camp::list<Ts...>,
655  void_ptr_wrapper dest,
656  void_ptr_wrapper src) const
657  {
658  camp::sink(((id_types == id) ? (impl<Ts>(dest, src), 0) : 0)...);
659  }
660 
661  template<typename T>
662  void impl(void_ptr_wrapper dest, void_ptr_wrapper src) const
663  {
664  T* dest_as_T = static_cast<T*>(dest.ptr);
665  T* src_as_T = static_cast<T*>(src.ptr);
666  new (dest_as_T) T(std::move(*src_as_T));
667  (*src_as_T).~T();
668  }
669  };
670 
674  struct host_invoker_type
675  {
677 
678  void operator()(void_cptr_wrapper obj, CallArgs... args) const
679  {
680  impl_helper(callable_indices {}, callable_types {}, obj,
681  std::forward<CallArgs>(args)...);
682  }
683 
684  private:
685  template<int... id_types, typename... Ts>
686  void impl_helper(camp::int_seq<int, id_types...>,
687  camp::list<Ts...>,
688  void_cptr_wrapper obj,
689  CallArgs... args) const
690  {
691  camp::sink(((id_types == id)
692  ? (impl<Ts>(obj, std::forward<CallArgs>(args)...), 0)
693  : 0)...);
694  }
695 
696  template<typename T>
697  void impl(void_cptr_wrapper obj, CallArgs... args) const
698  {
699  const T* obj_as_T = static_cast<const T*>(obj.ptr);
700  (*obj_as_T)(std::forward<CallArgs>(args)...);
701  }
702  };
703 
704  struct device_invoker_type
705  {
707 
708  RAJA_DEVICE void operator()(void_cptr_wrapper obj, CallArgs... args) const
709  {
710  impl_helper(callable_indices {}, callable_types {}, obj,
711  std::forward<CallArgs>(args)...);
712  }
713 
714  private:
715  template<int... id_types, typename... Ts>
716  RAJA_DEVICE void impl_helper(camp::int_seq<int, id_types...>,
717  camp::list<Ts...>,
718  void_cptr_wrapper obj,
719  CallArgs... args) const
720  {
721  camp::sink(((id_types == id)
722  ? (impl<Ts>(obj, std::forward<CallArgs>(args)...), 0)
723  : 0)...);
724  }
725 
726  template<typename T>
727  RAJA_DEVICE void impl(void_cptr_wrapper obj, CallArgs... args) const
728  {
729  const T* obj_as_T = static_cast<const T*>(obj.ptr);
730  (*obj_as_T)(std::forward<CallArgs>(args)...);
731  }
732  };
733 
734  using invoker_type = std::
735  conditional_t<use_host_invoke, host_invoker_type, device_invoker_type>;
736 
740  struct destroyer_type
741  {
743 
744  void operator()(void_ptr_wrapper obj) const
745  {
746  impl_helper(callable_indices {}, callable_types {}, obj);
747  }
748 
749  private:
750  template<int... id_types, typename... Ts>
751  void impl_helper(camp::int_seq<int, id_types...>,
752  camp::list<Ts...>,
753  void_ptr_wrapper obj) const
754  {
755  camp::sink(((id_types == id) ? (impl<Ts>(obj), 0) : 0)...);
756  }
757 
758  template<typename T>
759  void impl(void_ptr_wrapper obj) const
760  {
761  T* obj_as_T = static_cast<T*>(obj.ptr);
762  (*obj_as_T).~T();
763  }
764  };
765 
772  template<typename T, int... id_types, typename... Ts>
773  static constexpr id_type get_id(camp::int_seq<int, id_types...>,
774  camp::list<Ts...>)
775  {
776  id_type id {-1};
777  // quiet UB warning by sequencing assignment to id with list initialization
778  int unused[] {0,
779  (std::is_same<T, Ts>::value ? ((id = id_types), 0) : 0)...};
780  camp::sink(unused); // quiet unused var warning
781  return id;
782  }
783 
787  template<typename T,
788  bool uhi = use_host_invoke,
789  std::enable_if_t<uhi>* = nullptr>
790  static inline Dispatcher makeDispatcher()
791  {
792  static constexpr id_type id =
793  get_id<T>(callable_indices {}, callable_types {});
794  static_assert(id != id_type(-1), "T must be in direct_dispatch types");
795  return {mover_type {id}, host_invoker_type {id}, destroyer_type {id},
796  sizeof(T)};
797  }
798 
805  template<typename T,
806  typename CreateOnDevice,
807  bool uhi = use_host_invoke,
808  std::enable_if_t<!uhi>* = nullptr>
809  static inline Dispatcher makeDispatcher(CreateOnDevice&&)
810  {
811  static constexpr id_type id =
812  get_id<T>(callable_indices {}, callable_types {});
813  static_assert(id != id_type(-1), "T must be in direct_dispatch types");
814  return {mover_type {id}, device_invoker_type {id}, destroyer_type {id},
815  sizeof(T)};
816  }
817 
820  destroyer_type destroy;
821  size_t size;
822 };
823 
828 // template < typename T, typename Dispatcher_T >
829 // inline const Dispatcher_T* get_Dispatcher(work_policy const&);
830 
831 } // namespace detail
832 
833 } // namespace RAJA
834 
835 #endif // closing endif for header file include guard
#define RAJA_HOST_DEVICE
Definition: macros.hpp:65
#define RAJA_DEVICE
Definition: macros.hpp:66
Args args
Definition: WorkRunner.hpp:212
constexpr bool dispatcher_use_host_invoke(Platform platform)
Definition: Dispatcher.hpp:60
typename dispatcher_transform_types< dispatch_policy, holder_type >::type dispatcher_transform_types_t
Definition: Dispatcher.hpp:72
Definition: AlignedRangeIndexSetBuilders.cpp:35
RAJA wrapper for "multi-policy" and dynamic policy selection.
Definition: Dispatcher.hpp:52
RAJA_HOST_DEVICE DispatcherVoidConstPtrWrapper(const void *p)
Definition: Dispatcher.hpp:57
const void * ptr
Definition: Dispatcher.hpp:53
Definition: Dispatcher.hpp:42
void * ptr
Definition: Dispatcher.hpp:43
RAJA_HOST_DEVICE DispatcherVoidPtrWrapper(void *p)
Definition: Dispatcher.hpp:47
void operator()(void_ptr_wrapper dest, void_ptr_wrapper src) const
Definition: Dispatcher.hpp:646
static constexpr id_type get_id(camp::int_seq< int, id_types... >, camp::list< Ts... >)
Definition: Dispatcher.hpp:773
std::conditional_t< use_host_invoke, host_invoker_type, device_invoker_type > invoker_type
Definition: Dispatcher.hpp:735
camp::make_int_seq_t< id_type, 2+sizeof...(TNs)> callable_indices
Definition: Dispatcher.hpp:635
RAJA_DEVICE void operator()(void_cptr_wrapper obj, CallArgs... args) const
Definition: Dispatcher.hpp:708
void operator()(void_cptr_wrapper obj, CallArgs... args) const
Definition: Dispatcher.hpp:678
void operator()(void_cptr_wrapper obj, CallArgs... args) const
Definition: Dispatcher.hpp:546
void operator()(void_ptr_wrapper dest, void_ptr_wrapper src) const
Definition: Dispatcher.hpp:532
RAJA_DEVICE void operator()(void_cptr_wrapper obj, CallArgs... args) const
Definition: Dispatcher.hpp:555
std::conditional_t< use_host_invoke, host_invoker_type, device_invoker_type > invoker_type
Definition: Dispatcher.hpp:563
static Dispatcher makeDispatcher(CreateOnDevice &&)
Definition: Dispatcher.hpp:600
RAJA_DEVICE void operator()(void_cptr_wrapper, CallArgs...) const
Definition: Dispatcher.hpp:461
static Dispatcher makeDispatcher(CreateOnDevice &&)
Definition: Dispatcher.hpp:496
std::conditional_t< use_host_invoke, host_invoker_type, device_invoker_type > invoker_type
Definition: Dispatcher.hpp:465
void operator()(void_cptr_wrapper, CallArgs...) const
Definition: Dispatcher.hpp:456
void operator()(void_ptr_wrapper, void_ptr_wrapper) const
Definition: Dispatcher.hpp:448
static void s_move_construct_destroy(void_ptr_wrapper dest, void_ptr_wrapper src)
Definition: Dispatcher.hpp:119
static Dispatcher makeDispatcher(CreateOnDevice &&createOnDevice)
Definition: Dispatcher.hpp:206
void(*)(void_ptr_wrapper, void_ptr_wrapper) mover_type
Definition: Dispatcher.hpp:158
static RAJA_DEVICE void s_device_invoke(void_cptr_wrapper obj, CallArgs... args)
Definition: Dispatcher.hpp:140
static void s_host_invoke(void_cptr_wrapper obj, CallArgs... args)
Definition: Dispatcher.hpp:132
void(*)(void_cptr_wrapper, CallArgs...) invoker_type
Definition: Dispatcher.hpp:160
virtual void move_destroy(void_ptr_wrapper dest, void_ptr_wrapper src) const override
Definition: Dispatcher.hpp:272
void operator()(void_cptr_wrapper obj, CallArgs... args) const
Definition: Dispatcher.hpp:332
void operator()(void_ptr_wrapper dest, void_ptr_wrapper src) const
Definition: Dispatcher.hpp:322
virtual void invoke(void_cptr_wrapper obj, CallArgs... args) const override
Definition: Dispatcher.hpp:297
RAJA_DEVICE void operator()(void_cptr_wrapper obj, CallArgs... args) const
Definition: Dispatcher.hpp:343
virtual RAJA_DEVICE void invoke(void_cptr_wrapper obj, CallArgs... args) const override
Definition: Dispatcher.hpp:310
static Dispatcher makeDispatcher(CreateOnDevice &&createOnDevice)
Definition: Dispatcher.hpp:404
std::conditional_t< use_host_invoke, host_invoker_type, device_invoker_type > invoker_type
Definition: Dispatcher.hpp:350
Definition: Dispatcher.hpp:85
Definition: Dispatcher.hpp:68
Definition: WorkGroup.hpp:94
Dispatch using function pointers to make indirect function calls.
Definition: WorkGroup.hpp:77
Dispatch using virtual functions to make indirect function calls.
Definition: WorkGroup.hpp:83