2015-02-05 9 views
7

Muszę wywołać funkcję - template lub przeciążoną - dla każdego elementu w arbitralnej krotce. Mówiąc dokładniej, muszę wywołać tę funkcję na elementach, które są określone w krotce.Stosowanie func do elementów w std :: tuple w naturalnej (nie odwrotnej) kolejności

Na przykład. Mam krotki std::tuple<int, float> t{1, 2.0f}; i funkcjonalny

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

Potrzebuję struct/funkcję Apply, które, jeśli nazywa jak Apply<Lambda, int, float>()(Lambda(), t) będzie wydajność:

1; 2.0f; 

i nie 2.0f; 1;.

Zauważ, że znam rozwiązanie, jeśli pakiet parametrów "raw" jest przekazywany do funkcji i wiem, jak to zrobić dla krotek w odwrotnej kolejności. Ale po próba częściowo specjalizujący Apply zawiedzie:

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

kod jest kompilowany ale tylko pierwszy argument jest drukowany. Jednakże, jeśli mogę wymienić specjalizację ForwardsApplicator z

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

działa poprawnie - ale oczywiście tylko na krotki o długości 2. Jak to zrobić - jeśli to możliwe, elegancko - dla krotek dowolnej długości?

EDYCJA: Dzięki chłopaki za odpowiedzi! Wszystkie trzy są naprawdę proste i wyjaśniają problem ze wszystkich możliwych punktów widokowych.

Odpowiedz

6

Problemem jest to, że size...(Components) nie może być stosowany w specjalizacji dla nieznana lista typów Components. GCC narzeka na to z błędem:

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

Proponuję nieco inne podejście. Po pierwsze, należy przesunąć listę typu Components do parametru szablonu dla operator(), to znaczy:

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

Następnie odwrócić kolejność połączeń: najpierw zrobić wywołanie rekurencyjne, potem invokation z index-1 (tj wezwać ostatni element krotki). Rozpocznij tę rekursję od index = sizeof...(Components) i idź do index = 0, która jest noop (więc specjalizacja ma 0, niezależnie od sizeof...(Components), z którym był problem, o którym zacząłem mówić).

Aby nazwać, dodać funkcję do odliczenia szablon argumentu:

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

Wtedy łatwo wywołać, bez konieczności jakichkolwiek parametrów szablonu na stronie połączenia:

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

Live demo

+0

Oto odpowiedź, której szukałem: elegancki i bardzo zbliżony do projektu, który miałem na myśli. Dzięki! – Mischa

+1

Masz rację, że ten wyspecjalizowany nie szablonowy argument jest nielegalny. Myślę, że OP używa VC12, który nie wydaje żadnych błędów, ale też nie robi nic dobrego; problem został rozwiązany w CTP5 VC14, który wydaje miły błąd. Warto zauważyć, że Clang (3.5.0, a także trunk 228146) kompiluje kod bez diagnostyki i pasuje również do specjalizacji. Należy to zgłosić. – bogdan

2

Odliczanie w szablonie nie musi oznaczać, że przetwarzane są elementy krotki w tej samej kolejności. Prostym podejściem do przetwarzania krotki głowa do ogona rekursji głowy (w przeciwieństwie do ogona rekursji)

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

Wyjście jest

2 
4.6 
9

To jest podręcznik do sztuczki 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>()); 
} 

Demo.

std::index_sequence i przyjaciele to C++ 14, ale jest to rozszerzenie biblioteki i może być łatwo zaimplementowane w C++ 11. Możesz łatwo znaleźć pół tuzina implementacji na SO.

Powiązane problemy