2// ********************************************************************
3// * License and Disclaimer *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
28// Template definition file
30// Author: Jonathan Madsen, LBNL - November 2020
31// --------------------------------------------------------------------
33#if !defined(G4PROFILER_ICC_)
34# define G4PROFILER_ICC_ 1
37# include <type_traits>
39# include <initializer_list>
43// for index_sequence implementation
44# include "PTL/Globals.hh"
46# if defined(GEANT4_USE_TIMEMORY)
47# include <timemory/utility/utility.hpp>
50# if !defined(GEANT4_FOLD_EXPRESSION)
51# define GEANT4_FOLD_EXPRESSION(...) \
52 ::G4Impl::consume_parameters( \
53 ::std::initializer_list<int>{ (__VA_ARGS__, 0)... })
56# if !defined(G4PROFILER_ARG_SET)
57# define G4PROFILER_ARG_SET(...) G4TypeList<__VA_ARGS__>
60//----------------------------------------------------------------------------//
61// lightweight (w.r.t. compile-time) alternative to std::tuple that doesn't
62// store anything and cannot be instantiated because it has no definition. This
63// guards against meta-programming mistakes where:
64// std::function<void(const G4Step*)>
66// std::function<void(G4TypeList<const G4Step*>)>
67template <typename... Types>
70// this is used in G4Impl::Functors to add a common set of arguments to all of
72template <typename... Types>
73struct G4CommonTypeList;
75//--------------------------------------------------------------------------------------//
77template <typename... Types>
80template <typename... Types>
81struct G4TypeListSize<G4TypeList<Types...>>
83 static constexpr size_t value = sizeof...(Types);
86template <typename... Types>
87struct G4TypeListSize<std::tuple<Types...>>
89 static constexpr size_t value = std::tuple_size<std::tuple<Types...>>::value;
94 template <typename Tp>
95 std::string demangle()
97# if defined(GEANT4_USE_TIMEMORY)
98 return tim::demangle<Tp>();
100 return typeid(Tp).name();
104 template <typename... Tp>
105 void consume_parameters(Tp&&...)
107 //------------------------------------------------------------------------//
108 // don't provide a definition that works without G4TypeList
109 template <typename RetT, typename... Tail>
111 //------------------------------------------------------------------------//
112 template <typename RetT, typename... Tail>
113 struct Functors<RetT, G4TypeList<Tail...>>
115 using type = std::function<RetT(Tail...)>;
117 //------------------------------------------------------------------------//
118 template <typename RetT, typename... CommonT, typename... Tail>
119 struct Functors<RetT, G4CommonTypeList<CommonT...>, G4TypeList<Tail...>>
121 using type = std::function<RetT(CommonT..., Tail...)>;
123 //------------------------------------------------------------------------//
124 template <typename RetT, typename... Types, typename... Tail>
125 struct Functors<RetT, G4TypeList<G4TypeList<Types...>, Tail...>>
127 using type = std::tuple<std::function<RetT(Types...)>,
128 typename Functors<RetT, Tail>::type...>;
130 //------------------------------------------------------------------------//
131 template <typename RetT, typename... CommonT, typename... Types,
133 struct Functors<RetT, G4CommonTypeList<CommonT...>,
134 G4TypeList<G4TypeList<Types...>, Tail...>>
136 using type = std::tuple<
137 std::function<RetT(CommonT..., Types...)>,
138 typename Functors<RetT, G4CommonTypeList<CommonT...>, Tail>::type...>;
140 //------------------------------------------------------------------------//
141 template <typename RetT, typename... Tail>
142 using Functors_t = typename Functors<RetT, Tail...>::type;
147// this allows the generic invocation or assignment of a functor
149template <typename Type, typename FuncT, typename RetT = void>
152 using this_type = FuncHandler<Type, FuncT, RetT>;
154 // until Geant4 updates to C++14 as a minimum
155 template <typename Tp>
156 using decay_t = typename std::decay<Tp>::type;
157 template <bool Bv, typename Tp = void>
158 using enable_if_t = typename std::enable_if<Bv, Tp>::type;
159 template <size_t... Idx>
160 using index_sequence = PTL::mpl::index_sequence<Idx...>;
161 template <size_t NumT>
162 using make_index_sequence = PTL::mpl::make_index_sequence<NumT>;
164 static constexpr size_t size = std::tuple_size<FuncT>::value;
166 FuncHandler(FuncT& _functors)
167 : m_functors(_functors)
170 // overloading the assignment operator will let users
171 // be able to use one method for the G4ProfilerConfig
172 // despite the potential variants. Thus this is valid:
174 // GetLabelFunctor() = [](int i) { return std::to_string(i); }
175 // GetLabelFunctor() = [](float v) { return std::to_string(v); }
177 // but will only compile for types that are explicitly
178 // supported --> the assign function iterates through the
179 // specific variants at compile-time
180 template <typename Func>
181 void operator=(Func&& f)
183 assign(m_functors, std::forward<Func>(f), 0, make_index_sequence<size>{});
189 template <typename Tp>
190 static enable_if_t<std::is_same<decay_t<Tp>, bool>::value, Tp>
191 get_default_return_value()
196 template <typename Tp>
197 static enable_if_t<std::is_same<decay_t<Tp>, std::string>::value, Tp>
198 get_default_return_value()
200 // this may return an ugly mangled name but will at least but useful
201 // and can be demangled with c++filt
202 return std::string("label-functor-not-set-for-") + G4Impl::demangle<Tp>();
205 template <typename Tp>
206 static enable_if_t<std::is_pointer<decay_t<Tp>>::value, Tp>
207 get_default_return_value()
213 using return_t = decay_t<RetT>;
216 // NOTE: All references to "iterations" in the comments
217 // below refer to compile-time iterations, which are
218 // implemented through recusion below. Iterations stop
219 // when a valid statement has been found and thus necessitates
220 // four versions of the same function: two of these functions
221 // handle the end of the recursion 'sizeof...(Tail) == 0'
222 // and the first of these functions (1.a) is used if a valid
223 // statement is found and the second (1.b, if reached) introduces
224 // a compilation error. The third and fourth start the iteration
225 // when 'sizeof...(Tail) > 0'. If a valid statement is found
226 // in the third function (2.a), recursion stops. If not, the
227 // iteration is continued to the next index via the fourth
233 // this is the end of the iteration through the potential
234 // functor variants and the trailing '->' tests whether the
235 // functor can be called with the given arguments. The
236 // 'int' as the second parameter ensures (through overload
237 // resolution rules) that this gets tested before the
238 // function after this (1.b).
239 // If the size of 'FuncT' is equal to 1, then this is also
240 // the start of the iteration through the potential functor
242 template <typename Tp, size_t Idx, size_t... Tail, typename... Args,
243 enable_if_t<sizeof...(Tail) == 0, int> = 0>
244 static auto invoke(Tp& _obj, int, index_sequence<Idx, Tail...>,
246 -> decltype(std::get<Idx>(_obj)(std::forward<Args>(_args)...), return_t{})
248 // if the functor has been set, then execute it
249 if(std::get<Idx>(_obj))
250 return std::get<Idx>(_obj)(std::forward<Args>(_args)...);
253 std::stringstream ss;
254 ss << "Error! Functor "
255 << G4Impl::demangle<decltype(std::get<Idx>(_obj))>()
256 << " was not set for " << G4Impl::demangle<Type>();
257 throw std::runtime_error(ss.str());
259 // the default for booleans should return false
260 return get_default_return_value<return_t>();
265 // this is the end of the iteration through the potential
266 // functor variants and if this function is reached during
267 // compile-time, this means that the given arguments are
268 // not supported by any of the functors and will fail to
269 // compile. The 'long' as the second parameter ensures that
270 // it has lower precedence than the one above
271 template <typename Tp, size_t Idx, size_t... Tail, typename... Args,
272 enable_if_t<sizeof...(Tail) == 0, int> = 0>
273 static auto invoke(Tp&, long, index_sequence<Idx, Tail...>, Args&&...)
276 // this will cause a failure at compile-time.
277 // this ensures that this static assert is dependent
278 // on this function getting instantiated, simply putting
279 // 'false' here would result in compile-time failure
280 // even if no code ever instantiated this function
281 static_assert(!std::is_same<Tp, Tp>::value, "Error! No valid functor!");
282 throw std::runtime_error(
283 "Error! No valid functor! This should have caused a compilation error!");
289 // If the size of 'FuncT' is greater than one, this is the
290 // start of the iteration through the potential functor variants.
291 // This version will be used if the X in '-> decltype(X, Y)'
292 // is valid. If it is not valid, then overload resolution
293 // rules will dictate that the compiler will move on to the
294 // 'invoke' member function 2.b
295 template <typename Tp, size_t Idx, size_t... Tail, typename... Args,
296 enable_if_t<(sizeof...(Tail) > 0), int> = 0>
297 static auto invoke(Tp& _obj, int, index_sequence<Idx, Tail...>,
299 -> decltype(std::get<Idx>(_obj)(std::forward<Args>(_args)...), return_t{})
301 return std::get<Idx>(_obj)(std::forward<Args>(_args)...);
306 // If the above test was not valid, we discard the current index
307 // ('Idx') and proceed to the next index. If there is only
308 // one index remaining, then this will call proceed to the
309 // first invoke member function (1.a). If there are multiple
310 // indexes remaining, then this will proceed to the previous
311 // invoke member function (2.a) and this will continue until
312 // a valid match is found or will fail to compile.
313 template <typename Tp, size_t Idx, size_t... Tail, typename... Args,
314 enable_if_t<(sizeof...(Tail) > 0), int> = 0>
315 static auto invoke(Tp& _obj, long, index_sequence<Idx, Tail...>,
317 -> decltype(invoke(_obj, 0, index_sequence<Tail...>{},
318 std::forward<Args>(_args)...))
320 return invoke(_obj, 0, index_sequence<Tail...>{},
321 std::forward<Args>(_args)...);
325 // this uses the same principles as the invoke member function.
326 // See the comments there.
327 template <typename LhsT, typename RhsT, size_t Idx, size_t... Tail,
328 enable_if_t<sizeof...(Tail) == 0, int> = 0>
329 static auto assign(LhsT& _lhs, RhsT&& _rhs, int, index_sequence<Idx, Tail...>)
330 -> decltype((std::get<Idx>(_lhs) = std::forward<RhsT>(_rhs)), void())
332 std::get<Idx>(_lhs) = std::forward<RhsT>(_rhs);
335 // this uses the same principles as the invoke member function.
336 // See the comments there.
337 template <typename LhsT, typename RhsT, size_t Idx, size_t... Tail,
338 enable_if_t<sizeof...(Tail) == 0, int> = 0>
339 static void assign(LhsT&, RhsT&&, long, index_sequence<Idx, Tail...>)
341 // this will cause a failure at compile-time.
342 // this ensures that this static assert is dependent
343 // on this function getting instantiated, simply putting
344 // 'false' here would result in compile-time failure
345 // even if no code ever instantiated this function
346 static_assert(!std::is_same<LhsT, LhsT>::value,
347 "Error! No valid functor assignment!");
348 throw std::runtime_error(
349 "Error! No valid functor! This should have caused a compilation error!");
352 // this uses the same principles as the invoke member function.
353 // See the comments there.
354 template <typename LhsT, typename RhsT, size_t Idx, size_t... Tail,
355 enable_if_t<(sizeof...(Tail) > 0), int> = 0>
356 static auto assign(LhsT& _lhs, RhsT&& _rhs, int, index_sequence<Idx, Tail...>)
357 -> decltype((std::get<Idx>(_lhs) = std::forward<RhsT>(_rhs)), void())
359 std::get<Idx>(_lhs) = std::forward<RhsT>(_rhs);
362 // this uses the same principles as the invoke member function.
363 // See the comments there.
364 template <typename LhsT, typename RhsT, size_t Idx, size_t... Tail,
365 enable_if_t<(sizeof...(Tail) > 0), int> = 0>
366 static void assign(LhsT& _lhs, RhsT&& _rhs, long,
367 index_sequence<Idx, Tail...>)
369 assign(_lhs, std::forward<RhsT>(_rhs), 0, index_sequence<Tail...>{});
373 // overloading the call operator makes it generic to call
374 // the functors but will only compile for types that are
375 // explicitly supported --> the invoke function iterates
376 // through the specific variants at compile-time to
377 // ensure using SFINAE
378 template <typename... Args>
379 auto operator()(Args&&... _args)
380 -> decltype(std::declval<this_type>().invoke(std::declval<FuncT&>(), 0,
381 make_index_sequence<size>{},
382 std::forward<Args>(_args)...))
384 return invoke(m_functors, 0, make_index_sequence<size>{},
385 std::forward<Args>(_args)...);
389//----------------------------------------------------------------------------//