Context
Firstly, some context: I'm using an empty struct
called nothing
to emulate something similar to "regular void
" in order to prettify some interfaces that rely on chaining multiple function objects together.
struct nothing { };
Example usage:
when_all([]{ return 0; }, []{ }, []{ return 'a'; })
.then([](int, char){ }); // result of lambda in the middle ignored
In the above example, what's actually happening is that I'm packaging all the results of the function objects passed to when_all
in an std::tuple
, converting void
to nothing
(in this example: std::tuple<int, nothing, char>
), then I'm using a helper function called apply_ignoring_nothing
that invokes a function object by unpacking an std::tuple
, ignoring the elements that are nothing
.
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 function call_ignoring_nothing
with the following signature:
template <typename F, typename... Ts>
constexpr decltype(auto) call_ignoring_nothing(F&& f, Ts&&... xs);
This function will invoke f
by perfectly-forwarding all xs...
for which the compile-time is_nothing_v<T>
returns false
.
is_nothing_v
is 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 recursively. The base case only takes f
and simply invokes 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 f
's arguments if !is_nothing_v<decltype(f)>
through a lambda. It then recurses over 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 an iterative manner, possibly making use of pack expansion to filter out the arguments without recursion.
Is it possible to implement call_ignoring_nothing
without recursion? I couldn't think of any technique that allows arguments to be filtered out during pack expansion.
integral_constant<std::size_t, Is>...
). Pattern match on that. Expandget<FilteredIs>(tuple)...
. – Raganfilter
" - you can optimizejoin
to avoid being always recursive; an example of that can be found in metal. – Ragan