Вызовите каждого члена кортежа с результатом предыдущего вызова рекурсивно - PullRequest
5 голосов
/ 20 марта 2019

Допустим, у меня есть кортеж std::tuple<Operation<1>, Operation<2>, Operation<3>>.Operation<> имеет функцию-член с подписью SomeType someFunction(SomeType).То, что я хочу сделать, - это последовательно вызывать операции так, чтобы результирующий порядок вызовов был Operation<3>::someFunction(Operation<2>::someFunction(Operation<1>::someFunction())), и я получил бы окончательное значение SomeType.Как мне добиться этого с помощью шаблонов переменных (у меня есть доступ к C ++ 17)?

Я могу вызывать каждую функцию-член с помощью std::apply([](auto& ...x) { (..., x.someFunction()); }, tuple);, но какое выражение мне нужно вызвать someFunction() с помощьювывод предыдущего вызова?

Ответы [ 2 ]

4 голосов
/ 20 марта 2019

Полагаю, вы можете комбинировать std::apply() и свертывание шаблона с лямбдой следующим образом

   auto l = [&val](auto ... Ops) 
    { ((val = Ops.someFunc(val)), ...); };

Ниже приведен полный рабочий пример

#include <tuple>
#include <iostream>

template <int I>
struct Oper
 {
   static constexpr int someFunc (int i)
    { return i + I; }
 };

int main ()
 {
   std::tuple<Oper<1>, Oper<2>, Oper<3>, Oper<4>>  t;

   int val {}; // starting value

   auto l = [&val](auto ... Ops) 
    { ((val = Ops.someFunc(val)), ...); };

   std::apply(l, t);

   std::cout << val << std::endl;
 }
1 голос
/ 21 марта 2019
Решение

@ max66 элегантно и кратко, однако одно предостережение в том, что все ваши операции должны обрабатывать и возвращать один и тот же тип (как в вашем случае), я попытаюсь предложить более широкий подход.

Идеяполагаться на перегруженный operator>>, чтобы применить желаемую операцию к состоянию и следующему шагу.Для этого давайте сначала определим некоторые строительные блоки:

// Just to avoid the hassle of std::forwarding by hand everywhere
#define CPPFWD(x) std::forward<decltype(x)>(x)

// We do not want to pollute the global namespace with our special operator>>
namespace combine {

// This will make the appropriate functor for each step
template <typename T, typename Op>
auto make_operation(T&& tuple_element, Op&& op) {
    return [ el = CPPFWD(tuple_element),
             op = CPPFWD(op) ](auto&& input) mutable {
        return op(el, CPPFWD(input));
    };
}

template <typename Input, typename Op>
auto operator>>(Input&& input, Op&& op) {
    return CPPFWD(op)(CPPFWD(input));
}


} // ns combine

Теперь мы готовы заняться реализацией левой складки:

template <typename State, typename Tuple, typename Op, size_t... Is>
auto fold_left_impl(State&& state, Tuple&& tuple, Op&& op, std::index_sequence<Is...>) {
    using combine::make_operation;
    // We want our operator>> to be in the immediate scope here
    // to avoid selecting an inappropriate hypothetical overload 
    using combine::operator>>;
    using std::get;

    return (CPPFWD(state) >> ... >> make_operation(get<Is>(CPPFWD(tuple)), op));
}

Наконец, функция, предоставляемая конечному пользователю:

template <typename T>
using remove_cvref_t = std::remove_cv_t< std::remove_reference_t< T > >;

template <typename State, typename Tuple, typename Op>
auto fold_left(State&& state, Tuple&& tuple, Op&& op) {
    return fold_left_impl(
        CPPFWD(state),
        CPPFWD(tuple),
        CPPFWD(op),
        std::make_index_sequence< std::tuple_size< remove_cvref_t< Tuple > >::value > {} );
}

В вашем случае правильное использование будет следующим:

std::tuple<Operation<1>, Operation<2>, Operation<3>> t;
fold_left(
    0,
    t,
    [](auto&& op, auto&& in) {
        return CPPFWD(op).someFunc(CPPFWD(in));
    } );

Живой пример можно найти на Coliru

...