2015-03-05 30 views
7

Chcę napisać metodę klasy, która przyjmuje parametr szablonu pakiet, ale zerowe argumenty, a „iterate” nad typów:rekurencyjnej funkcji o zmiennej liczbie argumentów szablonu

struct Bar { 
    template <typename T, typename... Ts> 
    void foo() { 
     // something with T that involves Bar's members 
     foo<Ts...>(); 
    } 
}; 

Co jest preferowanym sposobem realizacji tego?

Odpowiedz

3

Możesz użyć następujących:

struct Bar { 
    template <typename... Ts> 
    void foo() { 
     int dummy[] = {0 /*Manage case where Ts is empty*/, 
         (bar<Ts>(), void() /* To avoid overload `operator,` */, 0)...}; 
     (void) dummy; // suppress warning for unused variable. 
    } 

    template <typename T> 
    void bar() 
    { 
     // something with T that involves Bar's members 
    } 

}; 

w C++ 17, może zostać uproszczone ze składanym wyrażenia:

struct Bar { 
    template <typename... Ts> 
    void foo() { 
     (static_cast<void>(bar<Ts>()), ...); 
    } 

    template <typename T> 
    void bar() 
    { 
     // something with T that involves Bar's members 
    } 

}; 
0

Lubię przeciążone funkcje i przy użyciu typelist:

#include <iostream> 
#include <typeinfo> 

template <typename ...Ts> struct typelist { }; 

void foo_impl(typelist<>) 
{ 
    // we are finished 
} 

template <typename T, typename ...Ts> 
void foo_impl(typelist<T, Ts...>) 
{ 
    std::cout << typeid(T).name() << ", "; 
    foo_impl(typelist<Ts...>{}); 
} 



template <typename ...Ts> 
void foo() 
{ 
    std::cout << "called with <"; 
    foo_impl(typelist<Ts...>{}); 
    std::cout << ">" << std::endl; 
} 


int main() 
{ 
    foo<int, char, float>(); 
} 
+0

huh? 'foo' nie przyjmuje argumentów. – MadScientist

+0

Jako wadę, wymaga N rekursywnych kroków dla typów N i tworzy instancje typów i funkcji, których długość nazwy wynosi O (N^2). Wykonanie powyższego z N skromnie dużym powoduje powolną kompilację i może prowadzić do binarnego wzdęcia, jeśli te nazwy nie zostaną usunięte przez kompilator. – Yakk

3
template<class...Fs> 
void do_in_order(Fs&&...fs) { 
    int _[]={0, (std::forward<Fs>(fs)(), void(), 0)...}; 
    (void)_; 
} 

ukrywa Składnia wymagana do wykonania pakietu obiektów funkcyjnych w kolejności od lewej do prawej.

Następnie:

struct Bar { 
    template <class... Ts> 
    void foo() { 
    do_in_order([&]{ 
     using T = Ts; 
     // code 
    }...); 
    } 
}; 

iw zgodnego kompilatora, będziemy uruchomić // code z T bycia każdego typu od lewej do prawej.

Należy pamiętać, że niektóre kompilatory podające się za kompilatory C++ 11 mogą nie skompilować powyższego.

Zaletą tej techniki jest ukrywanie nieprzyjemnego "rozszerzenia i oceny szablonów" w ramach funkcji o jednoznacznej nazwie. Raz piszesz: do_in_order i zwykle wystarcza prawie na każde użycie tej sztuczki z rozszerzeniem macierzy.

Istnieją dwa ważne powody używania tego rodzaju ezoterycznej składni zamiast "bardziej prostych" rozwiązań rekursywnych.

Po pierwsze, ułatwia to optymalizatorowi. Optymalizatory czasami rezygnują po stosie wywołań rekursywnych.

Po drugie, suma nazw długości znaków funkcji dla tradycyjnych funkcji rekursywnych rośnie w O (n^2). Jeśli używasz typów pomocniczych, całkowita długość nazw jest również równa O (n^2). O ile nie jesteś ostrożny, może to spowodować kompilację czasu, wydłużenie czasu połączenia i zwiększenie rozmiaru binarnego.

W C++ 1z planuje się składnię, która może sprawić, że ezoteryczne części powyższego będą mniej ezoteryczne.

+0

To jest całkiem schludne. Podobno gcc 4.9.2 jest jednym z tych "kompilatorów podających się za kompilatory C++ 11", które nie potrafią go skompilować. – Barry

+0

@Barry ayep. Clang Myślę, że działa. Z jakiegoś powodu, gcc uważa, że ​​nieskomplikowany pakiet parametrów na końcu instrukcji jest błędem. Silly gcc: statement może być tylko jedną częścią wzorca rozwinięcia. – Yakk

Powiązane problemy