问题
Suppose I have a range of tuples e.g. coming from the zip function. Do the functions which operate on that range have to be always unary or does there exist some transformation which unpacks the tuple into the function's arguments. Basically, I'd like to do the following:
auto r1 = {1, 2, 3, 4};
auto r2 = {'a', 'b', 'c', 'd'};
auto chars = view::zip(r1, r2) | view::transform([](int a, char x) { return x; });
instead of explicitly using std::tie or std::apply.
回答1:
It sounds like what you actually need is a function adaptor that explodes tuple arguments. Something like this (LIVE):
#include <type_traits>
#include <utility>
#include <range/v3/core.hpp>
#include <range/v3/utility/semiregular.hpp>
#include <range/v3/utility/tuple_algorithm.hpp>
template<class F>
struct decomposed_fn
{
private:
CONCEPT_ASSERT(ranges::CopyConstructible<F>());
ranges::semiregular_t<F> f_;
template<class FF>
struct caller
{
FF &f_;
template<class... Args>
RANGES_CXX14_CONSTEXPR auto operator()(Args &&...args)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::invoke(f_, std::forward<Args>(args)...)
)
};
public:
decomposed_fn() = default;
RANGES_CXX14_CONSTEXPR explicit decomposed_fn(F f)
noexcept(std::is_nothrow_move_constructible<F>::value)
: f_(std::move(f))
{}
template<class T>
RANGES_CXX14_CONSTEXPR auto operator()(T &&t)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::tuple_apply(caller<F>{f_}, std::forward<T>(t))
)
template<class T>
RANGES_CXX14_CONSTEXPR auto operator()(T &&t) const
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
ranges::tuple_apply(caller<F const>{f_}, std::forward<T>(t))
)
};
template<class F,
CONCEPT_REQUIRES_(ranges::CopyConstructible<std::decay_t<F>>())>
RANGES_CXX14_CONSTEXPR auto decomposed(F &&f)
RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT
(
decomposed_fn<std::decay_t<F>>(std::forward<F>(f))
)
with which you could formulate your range as:
auto chars = view::zip(r1, r2)
| view::transform(decomposed([](int, char x) { return x; }));
回答2:
Unfortunately there doesn't seem to be a transform-apply
view. An easy solution like the other answer is to adapt your lambda so that it is called with std::apply
(The equivalent of std::bind_front(std::apply<...>, your_lambda)
)
// C++20
template<typename F>
constexpr auto apply_to(F&& f) noexcept(noexcept([f=static_cast<F&&>(f)]{})) {
return [f=static_cast<F&&>(f)]<typename Tuple>(Tuple&& tuple) noexcept(noexcept(::std::apply(f, static_cast<Tuple&&>(tuple)))) -> decltype(auto) {
return ::std::apply(f, static_cast<Tuple&&>(tuple));
};
}
// C++17
// (Or C++14 with another std::apply implementation, like ranges::tuple_apply)
template<typename F>
constexpr auto apply_to(F&& f) {
return [f=static_cast<F&&>(f)](auto&& tuple) noexcept(noexcept(::std::apply(f, static_cast<decltype(tuple)&&>(tuple)))) -> decltype(auto) {
return ::std::apply(f, static_cast<decltype(tuple)&&>(tuple));
};
}
And just wrap your lambda as apply_to([](int a, char x) { /*...*/ })
.
Alternatively, a structured binding is quite short (in C++17)
// Be careful about excessive copying. Fine for the simple
// `std::tuple<char, int>`, but consider forwarding references
auto chars = view::zip(r1, r2) | view::transform([](auto zipped) {
auto [a, x] = zipped;
return x;
});
来源:https://stackoverflow.com/questions/54484633/unpacking-a-range-of-tuples-into-n-ary-function