4 listopada 2012

[C++] "Variadic template" oraz "fold expression"

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),
}

Brak komentarzy:

Prześlij komentarz