2014-09-05 18 views
56

Próbuję uczyć się szablonów i funkcji variadic. Nie mogę zrozumieć, dlaczego ten kod nie kompilacji:Rozszerzenie zestawu szablonów Variadic

template<typename T> 
static void bar(T t) {} 

template<typename... Args> 
static void foo2(Args... args) 
{ 
    (bar(args)...); 
} 

int main() 
{ 
    foo2(1, 2, 3, "3"); 
    return 0;  
} 

Kiedy kompilacji nie powiedzie się z powodu błędu:

Error C3520: 'args': parameter pack must be expanded in this context

(w funkcji foo2).

+1

Co jeśli 'Args' był pusty? – CoffeeandCode

Odpowiedz

86

Jednym z miejsc, w których może wystąpić rozszerzenie paczki, jest lista wzmocnionych inicjatorów . Można skorzystać z tego poprzez umieszczenie ekspansję wewnątrz listy inicjatora manekina tablicy:

template<typename... Args> 
static void foo2(Args &&... args) 
{ 
    int dummy[] = { 0, ((void) bar(std::forward<Args>(args)), 0) ... }; 
} 

Aby wyjaśnić zawartość inicjatora bardziej szczegółowo:

{ 0, ((void) bar(std::forward<Args>(args)), 0) ... }; 
    |  |  |      |  | 
    |  |  |      |  --- pack expand the whole thing 
    |  |  |      | 
    |  |  --perfect forwarding  --- comma operator 
    |  | 
    |  -- cast to void to ensure that regardless of bar()'s return type 
    |   the built-in comma operator is used rather than an overloaded one 
    | 
    ---ensure that the array has at least one element so that we don't try to make an 
    illegal 0-length array when args is empty 

Demo.

Ważną zaletą rozszerzenia w {} jest to, że gwarantuje ocenę od lewej do prawej.


z C++ 1z fold expressions, można po prostu napisać

((void) bar(std::forward<Args>(args)), ...); 
+13

Jeden z wariantów, jaki widziałem, to 'using expander = int []; expander {...}; 'ponieważ wtedy zmienna tablicowa nie ma nazwy i staje się jawnie oczywiste dla kompilatora, że ​​tablica nie musi mnie tworzyć ani używać. –

+2

Do czego służy drugie '0'? Ta tuż za operatorem przecinka –

+2

@AaronMcDaid Tak, że typ całego wyrażenia to 'int' i pasuje do typu elementu tablicy. –

36

Pakiety parametrów można rozwinąć tylko w ściśle określonej liście kontekstów, a operator , nie jest jednym z nich. Innymi słowy, nie jest możliwe użycie rozszerzenia paczki do wygenerowania wyrażenia składającego się z szeregu podwyrażeń ograniczonych przez operatora ,.

Zasadą jest „Rozbudowa może generować listy z , -separated wzorców gdzie , jest lista separator”. Operator , nie tworzy listy w sensie gramatycznym.

Aby wywołać funkcję dla każdego argumentu można użyć rekurencji (który jest podstawowym narzędziem w pole o zmiennej liczbie argumentów szablonu programisty):

template <typename T> 
void bar(T t) {} 

void foo2() {} 

template <typename Car, typename... Cdr> 
void foo2(Car car, Cdr... cdr) 
{ 
    bar(car); 
    foo2(cdr...); 
} 

int main() 
{ 
    foo2 (1, 2, 3, "3"); 
} 

Live example

+2

Cholera, po prostu założycie się, żebym odpowiedział na to * wstrząsa pięścią * ale prawdopodobnie powinieneś dodać perfekcyjne przekazanie odpowiedzi; to także "podstawowe narzędzie w oknie programisty szablonów variadic". – CoffeeandCode

+0

Dziękuję za odpowiedź. Wiem o implementacji rekursji. Po prostu chcę znaleźć obejście do kompilacji kodu bez rekurencji i nowej funkcji. –

+1

@ViacheslavDronov widziany tak, jakbyś używał szablonów: masz już mnóstwo funkcji generowanych przez kompilator, dlaczego nie dodać jednej do tej listy? – CoffeeandCode

13

SHAMELESS COPY [approved by its source]

kaczki parametr rozszerzać tylko w ściśle określonej liście kontekstów, a operator , nie jest jednym z nich. Innymi słowy, nie jest możliwe użycie rozszerzenia paczki do wygenerowania wyrażenia składającego się z szeregu podwyrażeń ograniczonych przez operatora ,.

Praktyczna zasada brzmi: "Rozszerzenie może generować listę , -odparowanych wzorów, gdzie , jest ogranicznikiem listy." Operator , nie tworzy listy w sensie gramatycznym.

Aby wywołać funkcję dla każdego argumentu można użyć rekurencji (który jest podstawowym narzędziem w pole o zmiennej liczbie argumentów szablonu programisty):

#include <utility> 

template<typename T> 
void foo(T &&t){} 

template<typename Arg0, typename Arg1, typename ... Args> 
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){ 
    foo(std::forward<Arg0>(arg0)); 
    foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...); 
} 

auto main() -> int{ 
    foo(1, 2, 3, "3"); 
} 

UŻYTECZNE NON-KOPIOWANA INFO

Inną rzeczą, której prawdopodobnie nie widzisz w tej odpowiedzi, jest użycie specyfikatora && i std::forward. W języku C++ specyfikator && może oznaczać jedną z dwóch rzeczy: referencje rvalue lub odwołania uniwersalne.

Nie przejdę do referencji rvalue, ale do kogoś pracującego z szablonami variadic; uniwersalne odniesienia to posłanie Boga.

Idealne Forwarding

Jednym z zastosowań std::forward i uniwersalnych odniesień są doskonałe przekazywanie typów do innych funkcji.

W przykładzie, jeśli mijamy int& do foo2 zostanie automatycznie zdegradowany do int powodu podpisu generowanego foo2 funkcji po szablonu odliczenia a jeśli chciał wtedy naprzód ten arg do innej funkcji, która go zmodyfikować ny odniesienie, otrzymasz niepożądane wyniki (zmienna nie zostanie zmieniona), ponieważ foo2 będzie przekazywać odniesienie do tymczasowego utworzonego przez przekazanie mu int. Aby obejść ten problem, określamy funkcję przekazywania, która ma przyjmować dowolny typ typu odniesienia do zmiennej (wartość rVR lub). Następnie, aby upewnić się, że przekazujemy dokładnie ten typ przekazywany w funkcji przekazywania, używamy wtedy std::forward, wtedy i tylko, a następnie zezwalamy na demotowanie typów; ponieważ jesteśmy teraz w punkcie, w którym ma to największe znaczenie.

Jeśli musisz, przeczytaj więcej na temat universal references i perfect forwarding; scott meyers jest świetny jako zasób.

1

Można użyć make_tuple dla dodatku, ponieważ wprowadza kontekst gdzie sekwencja , produkowane przez rozszerzenie jest ważna

make_tuple((bar(std::forward<Args>(args)), 0)...); 

Podejrzewam, że niewykorzystana/nienazwana/tymczasowa krotka zera, która jest produkowana, jest dająca się wyłapać przez kompilator i zoptymalizowana z dala

Demo

-1

wykonanie zlecenia nie jest gwarantowana do tego!

make_tuple((bar(std::forward<Args>(args)), 0)...); 

W tym przykładzie parametry będą drukowane w kolejności odwrotnej co najmniej przy GCC.

foo2(1, 2, 3, "3"); 

calling bar for 3 
calling bar for 3 
calling bar for 2 
calling bar for 1 
Powiązane problemy