Variadic templates#
W C++11 szablony mogą akceptować dowolną ilość (również zero) parametrów. Jest to możliwe dzięki użyciu specjalnego grupowego parametru szablonu tzw. parameter pack, który reprezentuje wiele lub zero parametrów szablonu.
Parameter pack#
Parameter pack może być
grupą parametrów szablonu
template<typename... Ts> // template parameter pack
class tuple
{
//...
};
tuple<int, double, string&> t1; // 3 arguments: int, double, string&
tuple<> empty_tuple; // 0 arguments
grupą argumentów funkcji szablonowej
template <typename T, typename... TArgs>
shared_ptr<T> make_shared(TArgs&&... params)
{
//...
}
auto sptr = make_shared<Gadget>(42, "ipad"s); // Args as template param: int, std::string
// Args as function param: int&&, std:string&&
Rozpakowanie paczki parametrów#
Podstawową operacją wykonywaną na grupie parametrów szablonu jest rozpakowanie jej za pomocą operatora ... (tzw. pack expansion).
template <typaname... Ts> // template parameter pack
struct X
{
tuple<Ts...> data; // pack expansion
};
Rozpakowanie paczki parametrów (pack expansion) może zostać zrealizowane przy pomocy wzorca zakończonego elipsą ...:
template <typaname... Ts> // template parameter pack
struct XPtrs
{
tuple<Ts const*...> ptrs; // pack expansion
};
XPtrs<int, string, double> ptrs; // contains tuple<int const*, string const*, double const*>
Forwardowanie paczki argumentów#
Najczęstszym przypadkiem użycia wzorca przy rozpakowaniu paczki parametrów jest implementacja perfect forwarding’u:
template <typename F, typename... TArgs>
void call(F f, TArgs&&... args)
{
f(std::forward<TArgs>(args)...);
}
void foo(int x, double y, string const& z)
{
//...
}
call(foo, 42, 3.14, "text"s); // calls foo(42, 3.14, "text")
Innym przykładem jest implementacja funkcji std::make_unique:
template <typename T, typename... TArgs>
std::unique_ptr<T> make_unique(TArgs&&... args)
{
return std::unique_ptr<T>(new T(forward<TArgs>(args)...);
}
Idiom Head/Tail#
Praktyczne użycie variadic templates wykorzystuje często idiom Head/Tail (znany również First/Rest).
Idiom ten polega na zdefiniowaniu wersji szablonu akceptującego dwa parametry:
pierwszego
Headdrugiego
Tailw postaci paczki parametrów
W implementacji wykorzystany jest parametr (lub argument) typu Head, po czym rekursywnie wywołana jest implementacja dla rozpakowanej
paczki parametrów typu Tail.
Dla szablonów klas idiom wykorzystuje specjalizację częściową i szczegółową (do przerwania rekurencji):
template <typename... Types>
struct Count;
template <typename Head, typename... Tail>
struct Count<Head, Tail...>
{
constexpr static int value = 1 + Count<Tail...>::value; // expansion pack
};
template <>
struct Count<>
{
constexpr static int value = 0;
};
//...
static_assert(Count<int, double, string&>::value == 3, "must be 3");
W przypadku szablonów funkcji rekurencja może być przerwana przez dostarczenie odpowiednio przeciążonej funkcji. Zostanie ona w odpowiednim momencie rozwijania rekurencji wywołana.
void print()
{}
template <typename T, typename... Tail>
void print(const T& arg1, const Tail&... args)
{
std::cout << arg1 << endl;
print(args...); // args expansion
}
Operator sizeof…#
Operator sizeof... umożliwia odczytanie na etapie kompilacji ilości parametrów w grupie.
template <typename... Types>
struct VerifyCount
{
static_assert(Count<Types...>::value == sizeof...(Types),
"Error in counting number of parameters");
};
Ograniczenia paczek parametrów#
Klasa szablonowa może mieć tylko jedną paczkę parametrów i musi ona zostać umieszczona na końcu listy parametrów szablonu:
template <size_t... Indexes, typename... Ts> // error
class Error;
Można obejść to ograniczenie w następujący sposób:
template <size_t... Indexes> struct IndexSequence {};
template <typename Indexes, typename Ts...>
class Ok;
Ok<IndexSequence<1, 2, 3>, int, char, double> ok;
Funkcje szablonowe mogą mieć więcej paczek parametrów:
template <int... Factors, typename... Ts>
void scale_and_print(Ts const&... args)
{
print(ints * args...);
}
scale_and_print<1, 2, 3>(3.14, 2, 3.0f); // calls print(1 * 3.14, 2 * 2, 3 * 3.0)
Warning
Wszystkie paczki w tym samym wyrażeniu rozpakowującym muszą mieć taki sam rozmiar.
scale_and_print<1, 2>(3.14, 2, 3.0f); // ERROR
“Nietypowe” paczki parametrów#
Podobnie jak w przypadku innych parametrów szablonów, paczka parametrów nie musi być paczką typów, lecz może być paczką stałych znanych na etapie kompilacji
template <size_t... Values>
struct MaxValue; // primary template declaration
template <size_t First, size_t... Rest>
struct MaxValue<First, Rest...>
{
static constexpr size_t rvalue = MaxValue<Rest...>::value;
static constexpr size_t value = (First < rvalue) ? rvalue : First;
};
template <size_t Last>
struct MaxValue<Last>
{
static constexpr size_t value = Last; // termination of recursive expansion
};
static_assert(MaxValue<1, 5345, 3, 453, 645, 13>::value == 5345, "Error");
Variadic Mixins#
Variadic templates mogą być skutecznie wykorzystane do implementacji klas mixin
template <typename... Mixins>
class X : public Mixins...
{
public:
X(Mixins... mixins) : Mixins(std::move(mixins))...
{}
};
struct A
{
int id_a;
A(int id) : id_a(id) { std::cout << "A" << std::endl; }
void foo() { std::cout << "A::foo()" << std::endl; }
};
struct B
{
std::string id_b;
B(std::string id) : id_b(std::move(id)) { std::cout << "B" << std::endl; }
void bar() { std::cout << "B::bar()" << std::endl; }
};
int main()
{
X<A, B> x{A{1}, B{"one"}};
x.foo();
x.bar();
}