Commit c55a2801 authored by Tiago Peixoto's avatar Tiago Peixoto

Merge branch 'improved-action-dispatch' into 'master'

Use lambdas and pack expansion to reduce template instantiations in action_dispatch

This change produces significant reductions in compiler memory use (4.15GB to 2.81GB in my `graph_assortativity` testcase) and compiler runtime (180m to 168m overall, 206s to 188s in the testcase) by:

1. Performing type iteration with parameter pack expansion where possible
2. Testing the stored `boost::any` types sequentially instead of all at once (M+N instead of M*N operations)

I see no significant change in runtime with this approach, though it should in theory be an improvement.

See merge request !33
parents 66d1db43 e67702c9
Pipeline #686 passed with stage
in 199 minutes and 1 second
......@@ -423,9 +423,83 @@ struct action_wrap
Action _a;
// this takes a functor and type ranges and iterates through the type
// combinations when called with boost::any parameters, and calls the correct
// function
// action_dispatch machinery
// a lightweight class for holding a list of types
template <class... T>
struct typelist {};
template <class... T>
constexpr auto to_typelist(std::tuple<T...>) -> typelist<T...>;
template <class Tuple>
using to_typelist_t = decltype(to_typelist(std::declval<Tuple>()));
// handling one typelist/value
// select a binding from the current list
template<class F, // function to bind
class... Ts, // current typelist
class... TRS, // remaining typelists
class Arg,
class... Args>
bool dispatch_loop(F f,
typelist<typelist<Ts...>, TRS...>,
Arg&& arg,
Args&&... args) // remaining args
using namespace boost::mpl;
// determine which one of the Ts we are looking at
// then recurse with the first argument of F bound accordingly
void *farg; // pointer to extracted value from boost::any
// we always will know what its type is
if constexpr (sizeof...(TRS) == 0)
// just one argument remains to be bound
// iterate over types, trying each
(((farg = boost::any_cast<Ts>(&arg))
? (f(*static_cast<Ts*>(farg)), true)
// try reference_wrapper instead
: ((farg = boost::any_cast<std::reference_wrapper<Ts>>(&arg))
? (f(static_cast<std::reference_wrapper<Ts>*>(farg)->get()), true)
: false)) || ...);
// helper function for setting up recursion
auto dl =
[f = std::move(f)](auto * a, // extracted value from boost::any
auto&&... args) // boost::any's yet to be processed
// create the new F with N-1 arguments
return dispatch_loop
([f = std::move(f), a](auto &&... fargs){
(((farg = boost::any_cast<Ts>(&arg))
? dl(static_cast<Ts*>(farg),
: ((farg = boost::any_cast<std::reference_wrapper<Ts>>(&arg))
? dl(&static_cast<std::reference_wrapper<Ts>*>(farg)->get(),
: false))
|| ...); // iterate over Ts...
// this takes a functor and type ranges, locates the correct combination
// from the boost::any parameters, and calls the correct function
template <class Action, class Wrap, class... TRS>
struct action_dispatch
......@@ -434,8 +508,11 @@ struct action_dispatch
template <class... Args>
void operator()(Args&&... args) const
bool found =
boost::mpl::nested_for_each<TRS...>(_a, std::forward<Args>(args)...);
using namespace boost::mpl;
bool found = dispatch_loop(_a, typelist<to_typelist_t<to_tuple_t<TRS>>...>{},
if (!found)
std::vector<const std::type_info*> args_t = {(&(args).type())...};
......@@ -63,55 +63,6 @@ namespace mpl
// of arguments according to the called types. If the cast is successful, the
// function will be called with those types, and true will be returned.
template <class Action, std::size_t N>
struct all_any_cast
all_any_cast(Action a, std::array<any*, N>& args)
: _a(a), _args(args) {}
template <class... Ts>
bool operator()(Ts*... vs) const
return dispatch(std::make_index_sequence<sizeof...(Ts)>(), vs...);
template <class T>
T* try_any_cast(boost::any& a) const
T* t = any_cast<T>(&a);
if (t != nullptr)
return t;
std::reference_wrapper<T>* tr = any_cast<std::reference_wrapper<T>>(&a);
if (tr != nullptr)
return &(tr->get());
return nullptr;
template <std::size_t... Idx, class... Ts>
bool dispatch(std::index_sequence<Idx...>, Ts*...) const
static_assert(sizeof...(Idx) == N,
"all_any_cast: wrong number of arguments");
std::tuple<std::add_pointer_t<Ts>...> args;
if (((std::get<Idx>(args) = try_any_cast<Ts>(*_args[Idx])) && ...))
// successful set of casts. Dereference and call action.
std::apply([this](auto*... arg){ _a(*arg...); }, args);
return true;
return false;
Action _a;
std::array<any*, N>& _args;
// recursion-free variadic version of for_each
template <class...>
struct for_each_variadic;
......@@ -143,6 +94,9 @@ struct to_tuple
to_tuple_imp<mpl::_1, mpl::_2>>::type type;
template <class Seq>
using to_tuple_t = typename to_tuple<Seq>::type;
// nested type loops via variadic templates
template <class...>
......@@ -179,17 +133,6 @@ struct inner_loop<Action, std::tuple<Ts...>, TR1, TRS...>
// final function
template <class TR1, class... TRS, class Action, class... Args>
bool nested_for_each(Action a, Args&&... args)
std::array<any*, sizeof...(args)> as{{&args...}};
auto b = all_any_cast<Action, sizeof...(args)>(a, as);
typedef decltype(b) action_t;
typedef typename to_tuple<TR1>::type tr_tuple;
typedef inner_loop<action_t, std::tuple<>, TRS...> inner_loop_t;
return for_each_variadic<inner_loop_t, tr_tuple>()(inner_loop_t(b));
template <class TR1, class... TRS, class Action>
void nested_for_each(Action a)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment