Применение func к элементам в std::tuple в естественном (не обратном) порядке

Мне нужно вызвать шаблонную или перегруженную функцию для каждого элемента в произвольном кортеже. Чтобы быть точным, мне нужно вызвать эту функцию для элементов, как они указаны в кортеже.

Например. У меня есть кортеж std::tuple<int, float> t{1, 2.0f}; и функционал

class Lambda{
public: 
   template<class T>
   void operator()(T arg){ std::cout << arg << "; "; }
};

Мне нужна некоторая структура/функция Apply, которая при вызове как Apply<Lambda, int, float>()(Lambda(), t) даст:

1; 2.0f; 

и НЕ 2.0f; 1;.

Обратите внимание, что я знаю решение, если в функцию передается «сырой» пакет параметров, и я знаю, как это сделать для кортежей в обратном порядке. Но следующая попытка частичной специализации Apply не удалась:

template<class Func, size_t index, class ...Components>
class ForwardsApplicator{
public:
    void operator()(Func func, const std::tuple<Components...>& t){
        func(std::get<index>(t));
        ForwardsApplicator<Func, index + 1, Components...>()(func, t);
    }
};

template<class Func, class... Components>
class ForwardsApplicator < Func, sizeof...(Components), Components... > {
public:
    void operator()(Func func, const std::tuple<Components...>& t){}
};

int main{
    ForwardsApplicator<Lambda, 0, int, float>()(Lambda{}, std::make_tuple(1, 2.0f));
}

Код компилируется, но печатается только первый аргумент. Однако, если я заменю специализацию ForwardsApplicator на

template<class Func, class... Components>
class ForwardsApplicator < Func, 2, Components... >{...}

он работает корректно, но, конечно, только для кортежей длины 2. Как это сделать - если возможно, изящно - для кортежей произвольной длины?

РЕДАКТИРОВАТЬ: Спасибо, ребята, за ваши ответы! Все три очень прямолинейны и объясняют проблему со всех возможных точек зрения.


person Mischa    schedule 05.02.2015    source источник


Ответы (3)


Проблема в том, что size...(Components) нельзя использовать в специализации для списка неизвестных типов Components. GCC жалуется на это с ошибкой:

prog.cpp:16:7: error: template argument 'sizeof... (Components)' involves template parameter(s)
 class ForwardsApplicator < Func, sizeof...(Components), Components... > {
       ^

Я предлагаю немного другой подход. Сначала переместите список типов Components в параметр шаблона для operator(), т.е.:

template<class ...Components>
void operator()(Func func, const std::tuple<Components...>& t) {
    ...
}

Затем измените порядок вызова: сначала выполните рекурсивный вызов, затем вызов с помощью index-1 (т. е. вызов последнего элемента кортежа). Начинайте эту рекурсию с index = sizeof...(Components) и идите до index = 0, что является noop (таким образом, специализация имеет 0, независимую от sizeof...(Components), о которой я начал говорить).

Чтобы помочь вызвать это, добавьте функцию для вывода аргумента шаблона:

// General case (recursion)
template<class Func, size_t index>
class ForwardsApplicator{
public:
    template<class ...Components>
    void operator()(Func func, const std::tuple<Components...>& t){
        ForwardsApplicator<Func, index - 1>()(func, t);
        func(std::get<index - 1>(t));
    }
};

// Special case (stop recursion)
template<class Func>
class ForwardsApplicator<Func, 0> {
public:
    template<class ...Components>
    void operator()(Func func, const std::tuple<Components...>& t){}
};

// Helper function for template type deduction
template<class Func, class ...Components>
void apply(Func func, const std::tuple<Components...>& t) {
    ForwardsApplicator<Func, sizeof...(Components)>()(func, t);
}

Затем его легко вызвать без необходимости каких-либо параметров шаблона на сайте вызова:

apply(Lambda{}, std::make_tuple(1, 2.0f));

Текущая демонстрация

person leemes    schedule 05.02.2015
comment
Это ответ, который я искал: элегантный и очень близкий к дизайну, который я имел в виду. Спасибо! - person Mischa; 05.02.2015
comment
Вы правы в том, что этот специализированный аргумент шаблона, не являющийся типом, является незаконным. Я думаю, что OP использует VC12, который не выдает никаких ошибок, но и не делает ничего хорошего; проблема была исправлена ​​в VC14 CTP5, которая выдает приятную ошибку. Стоит отметить, что Clang (3.5.0, а также транк 228146) компилирует код без диагностики и тоже соответствует специализации. Об этом следует сообщить. - person bogdan; 05.02.2015

Это хрестоматийный пример трюка integer_sequence.

template<class Func, class Tuple, size_t...Is>
void for_each_in_tuple(Func f, Tuple&& tuple, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)f(std::get<Is>(std::forward<Tuple>(tuple))), 0)... };
}

template<class Func, class Tuple>
void for_each_in_tuple(Func f, Tuple&& tuple){
    for_each_in_tuple(f, std::forward<Tuple>(tuple),
               std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
}

Демо.

std::index_sequence и друзья — это C++14, но это расширение чистой библиотеки, которое легко реализовать на C++11. Вы можете легко найти полдюжины реализаций на SO.

person T.C.    schedule 05.02.2015

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

#include <tuple>
#include <iostream>

// Our entry point is the tuple size
template<typename Tuple, std::size_t i = std::tuple_size<Tuple>::value>
struct tuple_applicator {
  template<typename Func> static void apply(Tuple &t, Func &&f) {
    // but we recurse before we process the element associated with that
    // number, reversing the order so that the front elements are processed
    // first.
    tuple_applicator<Tuple, i - 1>::apply(t, std::forward<Func>(f));
    std::forward<Func>(f)(std::get<i - 1>(t));
  }
};

// The recursion stops here.
template<typename Tuple>
struct tuple_applicator<Tuple, 0> {
  template<typename Func> static void apply(Tuple &, Func &&) { }
};

// and this is syntactical sugar
template<typename Tuple, typename Func>
void tuple_apply(Tuple &t, Func &&f) {
  tuple_applicator<Tuple>::apply(t, std::forward<Func>(f));
}

int main() {
  std::tuple<int, double> t { 1, 2.3 };

  // The generic lambda requires C++14, the rest
  // works with C++11 as well. Put your Lambda here instead.
  tuple_apply(t, [](auto x) { std::cout << x * 2 << '\n'; });
}

Выход

2
4.6
person Wintermute    schedule 05.02.2015