c++ - Controlling the unpacking of multiple variadic parameter packs for a fancier tuple_for_each -


background/motivation

i've been playing around vc++2015, looking @ ways of writing utility routines handle tuples , other variadic bits , pieces.

my first function of interest common-or-garden tuple_for_all function. function f , tuple t call in turn f(get<0>(t), f(get<1>(t), , on.

so far, straightforward.

template<typename tuple, typename function, std::size_t... indices> constexpr void tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     using swallow = int[];     static_cast<void>(swallow{ 0, (std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))), void(), 0)... }); }  template<typename function, typename tuple> constexpr void tuple_for_each(function&& f, tuple&& t) {     return tuple_for_each_aux(std::forward<function>(f), std::forward<tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<tuple>>::value>{}); } 

ok, works (modulo compilation , copy/paste errors occurred in cutting down).

but next thought function might return useful/interesting value in cases, , should capture it. naively, this:

template<typename tuple, typename function, std::size_t... indices> constexpr auto tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     return std::make_tuple(std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))); }  template<typename function, typename tuple> constexpr auto tuple_for_each(function&& f, tuple&& t) {     return tuple_for_each_aux(std::forward<function>(f), std::forward<tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<tuple>>::value>{}); } 

that's great when functions return value, since void annoyingly degenerate , can't make std::tuple<void>, won't work void-returning functions. can't directly overload return type, c++ gives tools handle this, sfinae:

template<typename function, typename tuple, std::size_t... indices, typename = std::enable_if_t<std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuple>>)>>::value>> constexpr void tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     using swallow = int[];     static_cast<void>(swallow{ 0, (std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))), void(), 0)... }); }  template<typename function, typename tuple, std::size_t... indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuple>>)>>::value>> constexpr decltype(auto) tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     return std::make_tuple(std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t)))...); }  template<typename function, typename tuple> constexpr decltype(auto) tuple_for_each(function&& f, tuple&& t) {     return tuple_for_each_aux(std::forward<function>(f), std::forward<tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<tuple>>::value>{}); } 

this nice enough. it'd nice if evaluation order consistent between 2 (the void version left-to-right, value-returning version compiler, right-to-left). can fix eschewing call std::make_tuple , instead brace initializing std::tuple instead. don't know if there's better decltype(std::make_tuple(...)) right type construct. there may be.

template<typename function, typename tuple, std::size_t... indices, typename = std::enable_if_t<std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuple>>)>>::value>> constexpr void tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     using swallow = int[];     static_cast<void>(swallow{ 0, (std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))), void(), 0)... }); }  template<typename function, typename tuple, std::size_t... indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuple>>)>>::value>> constexpr decltype(auto) tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     return decltype(std::make_tuple(std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t)))...)){std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))) ...}; }  template<typename tuple, typename function> constexpr decltype(auto) tuple_for_each(function&& f, tuple&& t) {     return tuple_for_each_aux(std::forward<function>(f), std::forward<tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<tuple>>::value>{}); } 

(incidentally, vc++ 2015 appears bugged right now; still doesn't use left-to-right evaluation braced initializer because optimizer team doesn't seem think it's important)

i'm more interested in std::enable_if_t check. not examine see if function returns non-void every type in tuple, first. really, should all-or-nothing. columbo's all_true technique takes care of us:

template <bool...> struct bool_pack;  template <bool... v> using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;  template<typename function, typename tuple, std::size_t... indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<function(std::tuple_element_t<indices, std::decay_t<tuple>>)>>::value...>::value>> constexpr void tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     using swallow = int[];     static_cast<void>(swallow{ 0, (std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))), void(), 0)... }); }  template<typename function, typename tuple, std::size_t... indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuple>>)>>::value>> constexpr decltype(auto) tuple_for_each_aux(function&& f, tuple&& t, std::index_sequence<indices...>) {     return decltype(std::make_tuple(std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t)))...)){std::forward<function>(f)(std::get<indices>(std::forward<tuple>(t))) ...}; }  template<typename function, typename tuple> constexpr decltype(auto) tuple_for_each(function&& f, tuple&& t) {     return tuple_for_each_aux(std::forward<function>(f), std::forward<tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<tuple>>::value>{}); } 

problem

but here's bit that's tricky. while tuple_for_each nice , useful, thought, if spiced little? how tuple_for_each takes function f , tuples t0, t1, etc. , computes f(get<0>(t0), get<0>(t1)...), f(get<1>(t0), get<1>(t1)...) , on?

naively we'd this:

    using swallow = int[];     static_cast<void>(swallow{ 0, ((std::forward<function>(f)(std::get<indices>(std::forward<tuples>(ts))...)), void(), 0)... }); 

naively, we'd first ... expand tuples, , second ... expand indices. parameter pack expansions don't offer kind of control. if expression before ... contains multiple parameter packs, ... tries unpack of them in parallel (vc++; emits compiler error different lengths), or cannot find parameter packs @ (g++; emits compiler error there no packs).

fortunately, case can handled additional layer of indirection separate out expansions:

template <bool...> struct bool_pack;  template <bool... v> using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;  template<size_t n, typename function, typename... tuples, typename = std::enable_if_t<std::is_void<std::result_of_t<function(std::tuple_element_t<n, std::decay_t<tuples>>...)>>::value>> constexpr void tuple_for_each_aux(function&& f, tuples&&... ts) {     return std::forward<function>(f)(std::get<n>(std::forward<tuples>(ts))...); }  template<typename function, typename... tuples, std::size_t... indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuples>>...)>>::value>::value>> constexpr void tuple_for_each_aux(function&& f, std::index_sequence<indices...>, tuples&&... ts) {     using swallow = int[];     static_cast<void>(swallow{ 0, (tuple_for_each_aux<indices>(std::forward<function>(f), std::forward<tuples>(ts)...), void(), 0)... }); }  template<std::size_t n, typename function, typename... tuples, typename = std::enable_if_t<!std::is_void<std::result_of_t<function(std::tuple_element_t<n, std::decay_t<tuples>>...)>>::value>> constexpr decltype(auto) tuple_for_each_aux(function&& f, tuples&&... ts) {     return std::forward<function>(f)(std::get<n>(std::forward<tuples>(ts))...); }  template<typename function, typename... tuples, std::size_t... indices, typename = std::enable_if_t<all_true<!std::is_void<std::result_of_t<function(std::tuple_element_t<0, std::decay_t<tuples>>...)>>::value>::value>> constexpr decltype(auto) tuple_for_each_aux(function&& f, std::index_sequence<indices...>, tuples&&... ts) {     return decltype(std::make_tuple(tuple_for_each_aux<indices>(std::forward<function>(f), std::forward<tuples>(ts)...)...)) { tuple_for_each_aux<indices>(std::forward<function>(f), std::forward<tuples>(ts)...)... }; }  template<typename function, typename tuple, typename... tuples> constexpr decltype(auto) tuple_for_each(function&& f, tuple&& t, tuples&&... ts) {     return tuple_for_each_aux(std::forward<function>(f), std::make_index_sequence<std::tuple_size<std::decay_t<tuple>>::value>{}, std::forward<tuple>(t), std::forward<tuples>(ts)...); } 

that works fine... except... pesky enable_ifs. had weaken them, testing first elements in tuples. now, it's not complete disaster, because inner-most expansion can perform check. it's not great. consider following:

struct functor {     int operator()(int a, int b) { return + b; }     double operator()(double a, double b) { return + b; }     void operator()(char, char) { return;  } };  int main() {     auto t1 = std::make_tuple(1, 2.0, 'a');     auto t2 = std::make_tuple(2, 4.0, 'b');     tuple_for_each(functor{}, t1, t2);     return 0; } 

the functor object needs force use of void path, because evaluating function on third tuple element returns void. our enable check looks @ first element. , because failure happens after sfinae-driven "overload" resolution, sfinae can't save here.

but equally, can't double-unpack enable_if_t expression same reason couldn't when invoking function: parameter pack expansion gets confused , tries iterate both @ same time.

and come unstuck. need indirection morally equivalent 1 used call function, can't see how write indirection such works.

any suggestions?

a holder of types:

template<class...> class typelist {}; 

an alias template compute result of applying f i-th element of each tuple in tuples:

template<class f, std::size_t i, class...tuples> using apply_result_type = decltype(std::declval<f>()(std::get<i>(std::declval<tuples>())...)); 

now compute list of result types:

template<class f, std::size_t...is, class... tuples> typelist<apply_result_type<f, is, tuples...>...>          compute_result_types(typelist<f, tuples...>, std::index_sequence<is...>);  template<class f, std::size_t size, class... tuples> using result_types = decltype(compute_result_types(typelist<f, tuples...>(),                                                    std::make_index_sequence<size>())); 

and check typelist has no void in it:

template<class... ts> all_true<!std::is_void<ts>::value...> do_is_none_void(typelist<ts...>);  template<class tl> using has_no_void_in_list = decltype(do_is_none_void(tl())); 

and actual sfinae (just showing one):

template<typename function, typename... tuples, std::size_t... indices,          typename = std::enable_if_t<!has_no_void_in_list<result_types<function,                                                                        sizeof...(indices),                                                                        tuples...>>{}>> constexpr void tuple_for_each_aux(function&& f, std::index_sequence<indices...>,                                    tuples&&... ts) {     using swallow = int[];     static_cast<void>(swallow{ 0, (tuple_for_each_aux<indices>(std::forward<function>(f), std::forward<tuples>(ts)...), void(), 0)... }); } 

demo.


Comments

Popular posts from this blog

python - Healpy: From Data to Healpix map -

c - Bitwise operation with (signed) enum value -

xslt - Unnest parent nodes by child node -