Variadic template
W C++11 do szablonów można przekazywać nie tylko
typy ale też wartości (
non-type template parameter). Przykład poniżej pokazuje obliczanie średniej w tych dwóch wersjach. Dodatkowo pojawia się nowy
sizeof...(), który potrafi powiedzieć z iloma parametrami mamy do czynienia.
#include <iostream>
using namespace std;
template <typename T>
int add(T last) {
return last;
}
template <typename T, typename... Nums>
int add(T first, Nums... nums) {
return first + add(nums...);
}
// non-type template parameter
template<int first, int... Nums>
double average1() {
return (static_cast<double>(first) + add(Nums...)) / (sizeof...(Nums) + 1);
}
// type template parameter
template <typename T, typename... Args>
double average2(T first, Args... args) {
return (static_cast<double>(first) + add(args...)) / (sizeof...(Args) + 1);
}
int main()
{
cout << average1<1, 2, 3, 4>() << endl;
cout << average2(1, 2, 3, 4) << endl;
}
Wynik:
2.5
2.5
Fold expression
W C++17 wprowadzono fold expression, które pozwala na stosowanie operatorów dla pakietu parametrów. W przykładzie ze średnią, nie trzeba więc stosować rekurencji. Trochę dziwne jest to że kompilator wymaga dwóch nawiasów, jeżeli chcemy rzutować wynik sumowania do
double.
#include <iostream>
using namespace std;
template <typename... Args>
double average(Args... args) {
return static_cast<double>((args + ...)) / sizeof...(Args);
}
int main()
{
cout << average(1, 2, 3, 4) << endl;
}
Wynik:
2.5
Zadanie domowe
Na GoingNative 2012 Andrei Alexandrescu, przedstawił swój wykład
"Variadic Templates are Funadic". Andrei bardzo lubi templejty, co widać ;). Variadic template mają w zamyśle być następcą/lepszą wersją wielokropka ("...") obecnego od czasów C, korzysta z niego np.
printf(), z którego każdy lubi korzystać. Do lepszego zrozumienia mechanizmu rozwijania variadic template, autor zaproponował zadanie domowe - produkt kartezjański. Ale, żeby się do niego zabrać, trzeba było zrozumieć wykład (sic!), z pomocą przyszedł poniższy link, gdzie, ktoś rozwinął w kod, to co przedstawił Andrei na slajdach:
Wpierw, trzeba zmusić kompilator/CMake (gcc 4.6.3) do pracy z nowym C++11, więc poniżej modyfikacja CMakeList.txt
project(variadic_cpp11)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
A teraz moje rozwiązanie - tworzą się pary wartości trzech typów i działa tylko dla wersji "A x A". W linijce 25 rozwinięcie, które było potrzebne, aby to osiągnąć. Aby można było przemnożyć między sobą dwa zbiory, trzeba by chyba skorzystać z nowego std::tuple. Ale w to się jeszcze nie wgłębiałem.
#include <iostream>
using namespace std;
template<class T>
int cartesianMult(T t)
{
return 0;
}
template<class T, class U, class... Us>
int cartesianMult(T t, U u, Us... us)
{
cout << endl << " (" << t << ", " << u << "),";
cartesianMult<T, Us...>(t, us...);
return 0;
}
template <class... T>
void fun(T...) {}
template <class... Ts>
void cartesianProduct(Ts... ts)
{
cout << "{";
fun( cartesianMult(ts, ts...)... );
cout << endl << "}" << endl;
}
int main()
{
cartesianProduct(1, 3.14, "abc");
return 0;
}
A oto wyniki i ciekawostka. Typy, a więc i wartości brane są w odwrotnej kolejności, niż były wkładane do funkcji.
{
(abc, 1),
(abc, 3.14),
(abc, abc),
(3.14, 1),
(3.14, 3.14),
(3.14, abc),
(1, 1),
(1, 3.14),
(1, abc),
}