Variadic templates
Niedawno marudziłem o makrach i funkcjach ze zmienną liczbą argumentów, więc dziś do uzupełnienia tego tematu wspomnę kilka słów o wzorcach ze zmienną liczbą parametrów, czyli Variadic templates, które niebawem staną się oficjalną częścią standardu języka C++.
Modelowa, prosta funkcja wzorcowa, która jako przykład występuje prawie w każdej wzmiance na temat „nowego ficzera”.
template<typename T> void print(const T& t) { std::cout << t; } template<typename T, typename... Args> void print(const T& t, const Args&... args) { print(t); print(args...); } |
Jak można zauważyć do określenia zmienności parametrów wykorzystano znane już wyrażenie …. Konstrukcja typename… określa, że definiujemy wzorzec z nieznaną liczbą parametrów, nieznanego typu, który zostanie wydedukowany „w praniu” przez kompilator.
Parametr wzorca Args, będzie w czasie konkretyzacji wzorca rozwijany w kolejne parametry wzorca, w naszym przypadku także w argumenty funkcji.
Czyli dla wywołanie funkcji:
void f(int a, int b, int c, std::string str) { print(a, b, c, str); } |
Kompilator „wygeneruje” funkcję wzorcową postaci:
template<typename T1, typename T2, typename T3, typename T4> void print(const T1& t1, const T2& t2, const T3& t3, const T4& t4) { std::cout << t1 << t2 << t3 << t4; } |
Która nastepnie zostanie skonkretyzowana dla kolejnych typów: int, int, int, std::string.
W sumie po co o tym pisze, skoro to takie oczywiste.
A co jeśli chcielibyśmy, aby argument przed przekazaniem do funkcji print został jeszcze przetworzony przez inna funkcję czy wyrażenie, aby otrzymać coś na kształt:
print(check(a), check(b), check(c)); |
Nic trudnego, wystarczy odpowiednio zając się argumentem:
template<typename T, typename... Args> void print(const T& t, const Args&... args) { print(check(t)); print(args...); } |
lub ciekawiej:
template<typename T, typename... Args> void print(const T& t, const Args&... args) { print(t); print(check(args)...); } |
To teraz pora na wzorce klas ;)
Zdefiniujmy sobie prosty wzorzec klasy, który będzie służył do wyznaczania ilości parametrów wzorca:
template<> struct count<> { static const int value = 0; }; template<typename T, typename... Args> struct count<T, Args...> { static const int value = 1 + count<Args...>::value; }; |
i proste użycie:
const int args_size = count<int, int, int, std::string>::value; |
Jego działanie oparte jest na zwijaniu i rozwijaniu parametrów wzorca ze zmienną liczbą parametrów (Packing and Unpacking Parameter Packs).
Najpopularniejszym zastosowaniem wzorców będzie tworzenie krotek (tuples), czyli struktur z uporządkowanym skończonym zbiorem składowych, cos jak std::pair, ale elastyczniejsze z dowolna liczbą elementów. Nie trzeba będzie się męczyć tak jak obecnie ;)
Przykładowe implementacje krotek, a także o wiele więcej ciekawych informacji, mozliwości i zastosowań Variadic templates można znaleźć w sieci. Osobiście mogę polecić stronę autora rozszerzenia Variadic Templates for GCC oraz artykuł Variadic Templates for C++0x (Douglas Gregor, Jaakko Järvi).