How might chain_apply()
be improved?
auto chain_apply(auto&& a, auto&& ...f)
{
auto const execute([&]<auto I>(auto&& self)
{
if constexpr(I)
{
return std::get<I>(
std::forward_as_tuple(f...))(
self.template operator()<I - 1>(self)
);
}
else
{
return std::apply(
std::get<0>(std::forward_as_tuple(f...)),
std::forward<decltype(a)>(a)
);
}
}
);
return execute.template operator()<sizeof...(f) - 1>(execute);
}
int main()
{
auto const inc([](auto const i) noexcept
{
return i + 1;
}
);
std::cout << chain_apply(std::tuple(0), inc, inc, inc, inc) << std::endl;
return 0;
}
EDIT:
No pipe operator this time, but consider:
std::tuple(0) | inc | inc | inc | inc;
1 Answer 1
A simpler approach
You can do this without packing the callables into a tuple and without using a recursive lambda. Consider:
auto chain_apply(auto&& a, auto&& f, auto&& ...fs)
{
if constexpr(sizeof...(fs)) {
return chain_apply(std::forward_as_tuple(std::apply(f, a)), fs...);
} else {
return std::apply(f, a);
}
}
(Some std::forward()
s omitted for the sake of clarity.)
It's not very useful to chain std::apply()
std::apply()
is used to unpack a tuple and apply it to a callable. However, the return value of most functions you would want to use is not a std::tuple
, instead it is a single value. So you can forward that as a tuple of one element, but that means you can only chain multiple callables if each one after the first only takes a single parameter.
If the function you pass does return a std::tuple
, you pack that tuple in another tuple. You could make chain_apply()
detect if the return value after applying a callable is a tuple, and if so not wrap it but pass it to the next function, but that seems like a heuristic that might fail in some situations.
If it only makes sense for a
to be a tuple with one element, then you could ask yourself if it needed to be a std::tuple
to begin with. I would also much rather be able to write:
std::cout << chain_apply(0, inc, inc, inc, inc) << '\n';
-
\$\begingroup\$ In my defense,
std::apply()
could also be improved in this way. \$\endgroup\$user1095108– user10951082022年02月12日 01:55:57 +00:00Commented Feb 12, 2022 at 1:55 -
\$\begingroup\$ Well, there's
std::invoke()
if you don't want to pass astd::tuple
. Maybe all this will be moot in C++23 or C++26 if we get UFCS. \$\endgroup\$G. Sliepen– G. Sliepen2022年02月12日 09:45:09 +00:00Commented Feb 12, 2022 at 9:45 -
\$\begingroup\$ Oh no, I'd like to chain coroutines :) That's what I'm working on. \$\endgroup\$user1095108– user10951082022年02月12日 10:38:01 +00:00Commented Feb 12, 2022 at 10:38
void
andtuple<>
returns (tuple<>
returns are auto-applied to the next function in the chain). \$\endgroup\$