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).

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *