Iterative filtering of arguments matching predicate at compile time

Context

First, some context: I use an empty struct called nothing to emulate something like "regular void " to prefix some interfaces that rely on combining several function objects together.

 struct nothing { }; 

Usage example:

 when_all([]{ return 0; }, []{ }, []{ return 'a'; }) .then([](int, char){ }); // result of lambda in the middle ignored 

In the above example, what actually happens, I collect all the results of function objects passed to when_all in std::tuple , converting void to nothing (in this example: std::tuple<int, nothing, char> ), then I am using a helper function called apply_ignoring_nothing , which calls the function object, unpacking std::tuple , ignoring the nothing elements.

 auto f_then = [](int, char){ }; auto args = std::tuple{0, nothing{}, 'a'}; apply_ignoring_nothing(f_then, args); // compiles 

apply_ignoring_nothing is implemented in terms of call_ignoring_nothing .


Question

I have a call_ignoring_nothing function with the following signature:

 template <typename F, typename... Ts> constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs); 

This function will call f by flawlessly forwarding all xs... for which compile time is_nothing_v<T> returns false .

is_nothing_v defined as follows:

 template <typename T> inline constexpr bool is_nothing_v = std::is_same_v<std::decay_t<T>, nothing>; 

The way I implemented call_ignoring_nothing is recursive. The base register only accepts f and simply calls it:

 #define FWD(x) ::std::forward<decltype(x)>(x) template <typename F> constexpr decltype(auto) call_ignoring_nothing(F&& f) { return returning_nothing_instead_of_void(FWD(f)); } 

The recursive case takes f , x and xs... and conditionally binds x as one of the arguments to f if !is_nothing_v<decltype(f)> via lambda. Then it recurses on call_ignoring_nothing passing the newly created lambda as f :

 template <typename F, typename T, typename... Ts> constexpr decltype(auto) call_ignoring_nothing(F&& f, T&& x, Ts&&... xs) { return call_ignoring_nothing( [&](auto&&... ys) -> decltype(auto) { if constexpr(is_nothing_v<T>) { return FWD(f)(FWD(ys)...); } else { return FWD(f)(FWD(x), FWD(ys)...); } }, FWD(xs)...); } 

I would like to implement call_ignoring_nothing in iterative mode, possibly using a package extension to filter out arguments without recursion.

Is it possible to implement call_ignoring_nothing without recursion? I could not come up with any method to filter the arguments during package expansion.

+8
c ++ templates metaprogramming template-meta-programming c ++ 17
source share
2 answers

Not so different from the Griwes suggestion, but ... I suppose you can use std::apply() , std::tuple_cat() , std::get() and tuples that are empty or with a value of is_nothing_v .

I mean ... something like [edit: improved with a suggestion from TC and an example from the PR itself (Vittorio Romeo)]

 template <bool B, typename ... Ts> constexpr auto pick_if (Ts && ... xs) { if constexpr ( B ) return std::forward_as_tuple(std::forward<Ts>(xs)...); else return std::tuple{}; } template <typename F, typename ... Ts> constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs) { return std::apply(f, std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...) ); } 

Below is a working example

 #include <tuple> #include <iostream> #include <type_traits> struct nothing { }; template <typename T> constexpr bool is_nothing_v = std::is_same<std::decay_t<T>, nothing>::value; template <bool B, typename ... Ts> constexpr auto pick_if (Ts && ... xs) { if constexpr ( B ) return std::forward_as_tuple(std::forward<Ts>(xs)...); else return std::tuple{}; } template <typename F, typename ... Ts> constexpr decltype(auto) call_ignoring_nothing (F && f, Ts && ... xs) { return std::apply(f, std::tuple_cat(pick_if<!is_nothing_v<Ts>>(std::forward<Ts>(xs))...) ); } float foo (int a, float b) { return a + b; } int main () { std::cout << call_ignoring_nothing(foo, nothing{}, 12, nothing{}, 2.3f, nothing{}); // print 14.3 } 

live example in wandbox

+6
source share

Here is another value that does not depend on tuple_cat . First, calculate the positions at which the bools package is true through the β€œregular” constexpr function template:

 template<class... Bools> constexpr int count(Bools... bs) { return (bool(bs) + ...); } template<bool... bs> constexpr std::array<std::size_t, count(bs...)> indices() { std::array<std::size_t, count(bs...)> ret = {}; std::size_t i = 0, j = 0; for(bool b : {bs...}) { if(b) { ret[j] = i; ++j; } ++i; } return ret; } 

Then convert the result to index_sequence :

 template<bool...bs, std::size_t...Is> constexpr auto indices_as_sequence_helper(std::index_sequence<Is...>) { return std::index_sequence<indices<bs...>()[Is]...>{}; } template<bool...bs> constexpr auto indices_as_sequence() { return indices_as_sequence_helper<bs...>(std::make_index_sequence<count(bs...)>()); } 

Then this is a simple forward_as_tuple + get index_sequence with index_sequence :

 template <typename F, typename... Ts, std::size_t... Is> constexpr decltype(auto) call_some(std::index_sequence<Is...>, F&& f, Ts&&... xs) { return std::forward<F>(f)( std::get<Is>(std::forward_as_tuple(std::forward<Ts>(xs)...))...); } template <typename F, typename... Ts> constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs) { return call_some(indices_as_sequence<!is_nothing_v<Ts>...>(), std::forward<F>(f), std::forward<Ts>(xs)...); } 
+1
source share

All Articles