Каковы некоторые виды использования параметров шаблона шаблона?
Я видел несколько примеров C++, использующих параметры шаблона шаблона (то есть шаблоны, которые принимают шаблоны в качестве параметров) для разработки класса на основе политики. Какое еще применение имеет эта техника?
9 ответов:
Я думаю, что вам нужно использовать синтаксис шаблона шаблона для передачи параметра, тип которого зависит от другого шаблона:
template <template<class> class H, class S> void f(const H<S> &value) { }
здесь
H
это шаблон, но я хотел, чтобы эта функция имела дело со всеми специализациямиH
.Примечание: я много лет программировал c++ и нуждался в этом только один раз. Я считаю, что это редко необходимая функция (конечно, удобно, когда вам это нужно!).
у меня есть я пытался придумать хорошие примеры, и, честно говоря, в большинстве случаев это не нужно, но давайте придумаем пример. Давайте притворимся, что
std::vector
не естьtypedef value_type
.Итак, как бы вы написали функцию, которая может создавать переменные правильного типа для элементов векторов? Это сработает.
template <template<class, class> class V, class T, class A> void f(V<T, A> &v) { // This can be "typename V<T, A>::value_type", // but we are pretending we don't have it T temp = v.back(); v.pop_back(); // Do some work on temp std::cout << temp << std::endl; }
Примечание мы
std::vector
имеет два параметра шаблона, тип и распределитель, поэтому нам пришлось принять оба из них. К счастью, из-за вычитания типа нам не нужно будет явно выписывать точный тип.который вы можете использовать следующим образом:
f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
или еще лучше, мы можем просто использовать:
f(v); // everything is deduced, f can deal with a vector of any type!
обновление: даже этот надуманный пример, хотя и иллюстративный, больше не является удивительным примером из-за введения c++11
auto
. Теперь та же функция может быть записана как:template <class Cont> void f(Cont &v) { auto temp = v.back(); v.pop_back(); // Do some work on temp std::cout << temp << std::endl; }
именно так я бы предпочел написать этот тип код.
на самом деле, usecase для параметров шаблона шаблона довольно очевидно. Как только вы узнаете, что c++ stdlib имеет зияющую дыру, не определяющую операторы вывода потока для стандартных типов контейнеров, вы продолжите писать что-то вроде:
template<typename T> static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v) { out << '['; if (!v.empty()) { for (typename std::list<T>::const_iterator i = v.begin(); ;) { out << *i; if (++i == v.end()) break; out << ", "; } } out << ']'; return out; }
тогда вы бы поняли, что код для вектора точно такой же, для forward_list такой же, на самом деле, даже для множества типов карт он все равно такой же. Эти классы шаблонов не имеют ничего общего, кроме мета-интерфейс / протокол, а также использование параметра template template позволяет зафиксировать общность во всех из них. Однако прежде чем приступить к написанию шаблона, стоит проверить ссылку, чтобы напомнить, что контейнеры последовательности принимают 2 аргумента шаблона - для типа значения и распределителя. Хотя распределитель по умолчанию, мы все равно должны учитывать его существование в нашем операторе шаблона
template<template <typename, typename> class Container, class V, class A> std::ostream& operator<<(std::ostream& out, Container<V, A> const& v) ...
вуаля, это будет работать автоматически для всех существующих и будущих контейнеров последовательности соблюдение стандартного протокола. Чтобы добавить карты в микс, нужно взглянуть на ссылку, чтобы отметить, что они принимают 4 шаблона params, поэтому нам понадобится другая версия оператора
кстати, с C + 11, который позволяет вариативные шаблоны (и, следовательно, должен позволять вариативные template template args), можно было бы иметь один оператор
#include <iostream> #include <vector> #include <deque> #include <list> template<typename T, template<class,class...> class C, class... Args> std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs) { os << __PRETTY_FUNCTION__ << '\n'; for (auto const& obj : objs) os << obj << ' '; return os; } int main() { std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 }; std::cout << vf << '\n'; std::list<char> lc { 'a', 'b', 'c', 'd' }; std::cout << lc << '\n'; std::deque<int> di { 1, 2, 3, 4 }; std::cout << di << '\n'; return 0; }
выход
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>] 1.1 2.2 3.3 4.4 std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>] a b c d std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>] 1 2 3 4
вот простой пример, взятый из 'Современное проектирование на С++ - обобщенное программирование и прикладные шаблоны проектирования' Андрей Александреску:
Он использует классы с параметрами шаблона шаблона для реализации шаблона политики:
// Library code template <template <class> class CreationPolicy> class WidgetManager : public CreationPolicy<Widget> { ... };
Он объясняет: как правило, класс узла уже знает или может легко вывести аргумент шаблона класса политики. В приведенном выше примере WidgetManager всегда управляет объектами типа Widget, поэтому требование от пользователя снова указать виджет в экземпляре CreationPolicy является избыточным и потенциально dangerous.In в этом случае код библиотеки может использовать параметры шаблона шаблона для указания политик.
эффект заключается в том, что клиентский код может использовать WidgetManager в более элегантный способ:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
вместо более громоздкого и подверженного ошибкам способа, который требовал бы определения без аргументов шаблона шаблона:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
вот еще один практический пример из моей библиотека сверточных нейронных сетей CUDA. У меня есть следующий шаблон класса:
template <class T> class Tensor
который фактически реализует n-мерных матриц манипуляции. Есть также шаблон дочернего класса:
template <class T> class TensorGPU : public Tensor<T>
который реализует ту же функциональность, но в GPU. Оба шаблона могут работать со всеми основными типами, такими как float, double, int и т. д И у меня также есть шаблон класса (упрощенно):
template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> > { TT<T> weights; TT<T> inputs; TT<int> connection_matrix; }
причина здесь, чтобы иметь синтаксис шаблона шаблона, потому что я могу объявить реализацию класса
class CLayerCuda: public CLayerT<TensorGPU, float>
который будет иметь как веса, так и входы типа float и на GPU, но connection_matrix всегда будет int, либо на CPU (указав TT = Tensor), либо на GPU (указав TT=TensorGPU).
скажем, вы используете CRTP для предоставления "интерфейса" для набора дочерних шаблонов; и родитель, и ребенок являются параметрическими в других аргументах шаблона:
template <typename DERIVED, typename VALUE> class interface { void do_something(VALUE v) { static_cast<DERIVED*>(this)->do_something(v); } }; template <typename VALUE> class derived : public interface<derived, VALUE> { void do_something(VALUE v) { ... } }; typedef interface<derived<int>, int> derived_t;
обратите внимание на дублирование 'int', который на самом деле является одним и тем же параметром типа, указанным для обоих шаблонов. Вы можете использовать шаблон шаблона для производных, чтобы избежать этого дублирования:
template <template <typename> class DERIVED, typename VALUE> class interface { void do_something(VALUE v) { static_cast<DERIVED<VALUE>*>(this)->do_something(v); } }; template <typename VALUE> class derived : public interface<derived, VALUE> { void do_something(VALUE v) { ... } }; typedef interface<derived, int> derived_t;
обратите внимание, что вы исключаете прямое предоставление других параметров шаблона производные шаблон; "интерфейс" по-прежнему получает их.
Это также позволяет создавать typedefs в "интерфейсе", которые зависят от параметров типа, которые будут доступны из производного шаблона.
выше typedef не работает, потому что вы не можете typedef к неопределенному шаблону. Это работает, однако (и C++11 имеет встроенную поддержку шаблонов typedefs):
template <typename VALUE> struct derived_interface_type { typedef typename interface<derived, VALUE> type; }; typedef typename derived_interface_type<int>::type derived_t;
вам нужен один derived_interface_type для каждого экземпляра производный шаблон к сожалению, если нет другого трюка, который я еще не изучил.
в решении с вариационными шаблонами, предоставленными pfalcon, мне было трудно фактически специализировать оператор ostream для std::map из-за жадного характера вариационной специализации. Вот небольшая ревизия, которая сработала для меня:
#include <iostream> #include <vector> #include <deque> #include <list> #include <map> namespace containerdisplay { template<typename T, template<class,class...> class C, class... Args> std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs) { std::cout << __PRETTY_FUNCTION__ << '\n'; for (auto const& obj : objs) os << obj << ' '; return os; } } template< typename K, typename V> std::ostream& operator << ( std::ostream& os, const std::map< K, V > & objs ) { std::cout << __PRETTY_FUNCTION__ << '\n'; for( auto& obj : objs ) { os << obj.first << ": " << obj.second << std::endl; } return os; } int main() { { using namespace containerdisplay; std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 }; std::cout << vf << '\n'; std::list<char> lc { 'a', 'b', 'c', 'd' }; std::cout << lc << '\n'; std::deque<int> di { 1, 2, 3, 4 }; std::cout << di << '\n'; } std::map< std::string, std::string > m1 { { "foo", "bar" }, { "baz", "boo" } }; std::cout << m1 << std::endl; return 0; }
вот с чем я столкнулся:
template<class A> class B { A& a; }; template<class B> class A { B b; }; class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>> { };
можно решить:
template<class A> class B { A& a; }; template< template<class> class B> class A { B<A> b; }; class AInstance : A<B> //happy { };
или (работает код):
template<class A> class B { public: A* a; int GetInt() { return a->dummy; } }; template< template<class> class B> class A { public: A() : dummy(3) { b.a = this; } B<A> b; int dummy; }; class AInstance : public A<B> //happy { public: void Print() { std::cout << b.GetInt(); } }; int main() { std::cout << "hello"; AInstance test; test.Print(); }
вот один обобщенный из того, что я только что использовал. Я отправляю его, так как это очень простой пример, и он демонстрирует практический случай применения наряду с аргументами по умолчанию:
#include <vector> template <class T> class Alloc final { /*...*/ }; template <template <class T> class allocator=Alloc> class MyClass final { public: std::vector<short,allocator<short>> field0; std::vector<float,allocator<float>> field1; };
это улучшает читаемость вашего кода, обеспечивает дополнительную безопасность типов и экономит некоторые усилия компилятора.
скажем, вы хотите распечатать каждый элемент контейнера, вы можете использовать следующий код без параметра шаблона шаблона
template <typename T> void print_container(const T& c) { for (const auto& v : c) { std::cout << v << ' '; } std::cout << '\n'; }
или с параметром шаблона шаблон
template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType> void print_container(const ContainerType<ValueType, AllocType>& c) { for (const auto& v : c) { std::cout << v << ' '; } std::cout << '\n'; }
предположим, что вы передаете целое число say
print_container(3)
. В первом случае шаблон будет создан компилятором, который будет жаловаться на использованиеc
in цикл for, последний не будет создавать экземпляр шаблона вообще, поскольку соответствующий тип не может быть найден.вообще говоря, если ваш класс шаблона/функция предназначена для обработки класса шаблона в качестве параметра шаблона, лучше сделать это ясно.