Created
October 12, 2017 23:34
-
-
Save kubatrt/3fdd027d50a808cfe7b2b4dbb6af6396 to your computer and use it in GitHub Desktop.
Print variadic template arguments, sepparating by comma.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
template <typename... Args> | |
void printVA(std::ostream& out, Args&&... args) | |
{ | |
using expander = int[]; | |
(void)expander { (void(out << ',' << std::forward<Args>(args)),0)... }; | |
out << std::endl; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
References:
https://stackoverflow.com/questions/27375089/what-is-the-easiest-way-to-print-a-variadic-parameter-pack-using-stdostream
http://en.cppreference.com/w/cpp/language/operator_other
Explanation in PL:
nie da się rozwinąć w C++ 14, parameter packu do listy osobnych instrukcji (czyt. czegoś rozdzielonego średnikiem, bądź dowolnym operatorem - to drugie możliwe jest już w C++ 17 BTW), więc trzeba sobie radzić inaczej
operator '...' rozwija parameter pack w listę parametrów oddzielonych operatorem przecinka
czyli: 'Tp... args', po rozwinięciu 'args...' zmienia się w coś a'la 'arg0, arg1, arg2' (itd)
na szczęście, arg można zawinąć w jakąś operację i rozwinąć całość, np:(args + 1)...zmienia się w np:args0 + 1, args1 + 1, args2 + 1
teraz, można to wykorzystać, tylko trzebaby taką listę porozdzielanych wyrażeń gdzieś przekazać... Np do funkcji
z funkcją jest niestety problem: nie masz gwarancji kolejności wykonania
więc mogą się tak zawinięte operacje, zależnie od kompilatora, architektury itp wykonać w zdupnej kolejności
rozwiązaniem jest lista inicjalizacyjna, która taką kolejność gwarantuje ; )
stąd w przykładzie "unsing expander = int[]" -> gość kolejnymi, rozwiniętymi wyrażeniami inicjuje tablicę intów
teraz problem numer dwa: dlaczego każda operacja musi zwracać inta? Co jeśli nie chcemy nic zwracać?
w C++14 w zasadzie coś musimy
wtedy przychodzi na ratunek operator przecinka działa tak, że dla wyrażenia "A, B" wykonuje A i B i zwraca to co zwraca B
(pamiętaj że operacja << na ostreamach zwraca ostream&, który nas nie interesuje)
więc żeby tą wartość zwracaną olać, jest opakowana w konwersję na void - tylko po to żeby dać znać kompilatorowi żeby to olał. Moim zdaniem to jest niepotrzebne, to '0' na końcu nawiasu jest tym drugim wyrażeniem, które jest wpisywane do tablicy intów
moim zdaniem to jest kiepskie rozwiązanie - po kiego grzyba na stosie budować w ogóle jakąkolwiek tablicę? W swoim szkoleniu Dominiaczek podawał lepsze rozwiązanie, które nie trzyma kciuków za optymalizator ; )
jak będziesz zdesperowany dalej, to mogę podejść ; P
Teraz pytanie dlaczego tak, a tak jak w rozwiązaniu z 4 upvote'ami?
Odpowiedź: bo generuje pseudorekursywny ciąg wywołań funkcji. Do 2, 3 elementów to jest nawet ok, chociaż dla różnych zestawów typów kompilator wygeneruje n różnych funkcji co jest po prostu bez sensu, gorzej dla większej ilości typów: liczba nowych sygnatur funkcji będzie ogromna, do tego dochodzą problemy z optymalizatorem: clang'owy po prostu odpuszcza optymalizowanie takiego gąszcza wywołań i nie optymalizuje nic, co jest mega niewydajne. W skrajnych wypadkach kończy się to końcem stosu w poprawnie kompilującym się programie ;>
tak, to samo przy wywołaniach funkcji
operator przecinka jest w tym blobie:
(out << ',' << std::forward(args) , 0) ...
zwróć uwagę że wszystko przed ... jest w nawiasie
dzieli się na dwie rzeczy przedzielone przecinkiem:
out << ',' << std::forward(args)
i tutaj operator przecinka już ma zastosowanie ; )
trick z operatorem przecinka ma konkretne zastosowanie jeszcze w jednym miejscu:
decltype(foo(123), 0)
decltype zwróci typ tylko dla "0" i oleje kompletnie typ zwracany funkcji foo, ale żeby wyrażenie było kompilowalne, foo(123) musi być poprawnym wyrażeniem
tak się robi SFINAE od C++11 - wrzucasz do decltype jaką funkcję ma wywoływać Twój podany typ, a po nim podajesz np 0 po przecinku, żeby olać typ zwracany
ma to swoje zastosowania, ale to osobna bajka ; )