Unpacking a range of tuples into n-ary function

拥有回忆 提交于 2021-01-27 12:45:50

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!