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
.
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.
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 value according the value of is_nothing_v
.
I mean... something like [edit: improved with a suggestion from T.C. and an example from the OP 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))...)
);
}
The following 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 on wandbox
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With