Прилагане на 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 и също trunk 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