Распаковка диапазона кортежей в n-арную функцию

Предположим, у меня есть несколько кортежей, например. поступает из функции zip. Должны ли функции, которые работают с этим диапазоном, всегда быть унарными, или существует какое-то преобразование, которое распаковывает кортеж в аргументы функции. По сути, я хотел бы сделать следующее:

  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; });

вместо явного использования std :: tie или std :: apply.


person kyku    schedule 01.02.2019    source источник
comment
r1 и r2 не являются кортежами. Это std::initializer_list<int> и std::initializer_list<char> соответственно.   -  person NathanOliver    schedule 01.02.2019
comment
@NathanOliver, но я считаю, что view :: zip (r1, r2) - это диапазон std :: tuple ‹int, char›   -  person kyku    schedule 01.02.2019


Ответы (2)


Похоже, что вам действительно нужен адаптер функции, который взрывает аргументы кортежа. Примерно так (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))
)

с помощью которого вы могли бы сформулировать свой диапазон как:

auto chars = view::zip(r1, r2)
    | view::transform(decomposed([](int, char x) { return x; }));
person Casey    schedule 01.02.2019
comment
не могли бы вы объяснить цель этого дополнительного вспомогательного класса caller? - person kyku; 02.02.2019
comment
@kyku caller здесь, потому что я хотел использовать invoke вместо того, чтобы tuple_apply напрямую вызывать адаптированную функцию. Я не думаю, что этим можно добиться многого, но это хорошо согласуется с тем, как остальная часть дизайна Ranges обрабатывает вызываемые объекты. - person Casey; 07.02.2019

К сожалению, transform-apply представления не существует. Простое решение, как и другой ответ, - адаптировать вашу лямбду так, чтобы она вызывалась с std::apply (эквивалент 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));
    };
}

И просто оберните лямбду как apply_to([](int a, char x) { /*...*/ }).


В качестве альтернативы структурированная привязка довольно короткая (в 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;
    });
person Artyer    schedule 23.01.2021