Я видел несколько примеров С++ с использованием параметров шаблона шаблона (то есть шаблонов, которые принимают шаблоны в качестве параметров) для разработки дизайна на основе политик. Какое другое использование имеет этот метод?
Каковы некоторые параметры шаблона шаблона?
Ответ 1
Я думаю, вам нужно использовать синтаксис шаблона шаблона для передачи параметра, тип которого является шаблоном, зависящим от другого шаблона, например:
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;
}
именно так я бы предпочел написать код такого типа.
Ответ 2
Собственно, usecase для параметров шаблона шаблона довольно очевиден. Как только вы узнаете, что в С++ 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 - это то же самое, на самом деле даже для множества типов карт это все равно. Эти классы шаблонов не имеют ничего общего, кроме мета-интерфейса/протокола, и использование шаблона шаблона позволяет зафиксировать общность во всех них. Прежде чем приступить к написанию шаблона, стоит проверить ссылку на отзыв, что контейнеры последовательностей принимают 2 аргумента шаблона - для типа значения и распределителя. В то время как распределитель по умолчанию, мы все равно должны учитывать его существование в нашем операторе шаблона <:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
Voila, которая будет работать автоматически для всех существующих и будущих контейнеров последовательности, придерживающихся стандартного протокола. Чтобы добавить карты в микс, вам нужно взглянуть на ссылку, чтобы отметить, что они принимают 4 шаблонных параметра, поэтому нам понадобится другая версия оператора < < выше с параметром шаблона шаблона 4-arg. Мы также увидим, что std: pair пытается отобразиться с помощью оператора 2-arg < для типов последовательностей, которые мы определили ранее, поэтому мы предоставили бы специализацию только для std:: pair.
Btw, с C + 11, который допускает вариативные шаблоны (и, следовательно, должен допускать вариационные шаблонные шаблонные аргументы), можно было бы иметь один оператор < чтобы править ими всеми. Например:
#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
Ответ 3
Вот простой пример, взятый из " Андрея Александреску" "Современный C++ дизайн - общие шаблоны программирования и применяемые модели проектирования":
Он использует классы с параметрами шаблона шаблона для реализации шаблона политики:
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
};
Он объясняет: как правило, хост-класс уже знает или может легко вывести аргумент шаблона класса политики. В приведенном выше примере WidgetManager всегда управляет объектами типа Widget, поэтому требование пользователя снова указывать Widget в экземпляре CreationPolicy является избыточным и потенциально опасным. В этом случае код библиотеки может использовать параметры шаблона шаблона для определения политик.
В результате клиентский код может использовать "WidgetManager" более элегантным способом:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
Вместо более обременительного и подверженного ошибкам способа, который потребовалось бы для определения, в котором отсутствуют аргументы шаблона:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
Ответ 4
Вот еще один практический пример из моей 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).
Ответ 5
Предположим, вы используете 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', который фактически является тем же самым параметром типа, который указан для обоих шаблонов. Вы можете использовать шаблон шаблона для DERIVED, чтобы избежать этого дублирования:
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 в неуказанный шаблон. Однако это работает (и С++ 11 имеет встроенную поддержку шаблонов typedefs):
template <typename VALUE>
struct derived_interface_type {
typedef typename interface<derived, VALUE> type;
};
typedef typename derived_interface_type<int>::type derived_t;
Для каждого экземпляра производного шаблона, к сожалению, вам понадобится один файл output_interface_type, если нет другого трюка, который я еще не изучил.
Ответ 6
Это то, с чем я столкнулся:
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();
}
Ответ 7
В решении с вариационными шаблонами, предоставляемыми 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;
}
Ответ 8
Здесь один обобщен из того, что я только что использовал. Я отправляю его, так как это очень простой пример, и он демонстрирует практический пример использования вместе с аргументами по умолчанию:
#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;
};
Ответ 9
Это улучшает читаемость вашего кода, обеспечивает дополнительную безопасность и сохраняет некоторые усилия компилятора.
Предположим, вы хотите напечатать каждый элемент контейнера, вы можете использовать следующий код без параметра шаблона шаблона
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';
}
Предположим, что вы передаете целое число print_container(3)
. В первом случае шаблон будет создан компилятором, который будет жаловаться на использование c
в цикле for, последний не будет создавать экземпляр шаблона вообще, так как не может быть найден подходящий тип.
Вообще говоря, если ваш шаблонный класс/функция спроектирован для обработки класса шаблона в качестве параметра шаблона, лучше прояснить его.
Ответ 10
Я нашел лучшее объяснение параметров шаблона здесь
Ответ 11
Я использую его для версионных типов.
Если у вас есть тип, созданный с помощью шаблона, такого как MyType<version>
, вы можете написать функцию, в которой вы можете захватить номер версии:
template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
assert(Version > 2 && "Versions older than 2 are no longer handled");
...
switch (Version)
{
...
}
}
Таким образом, вы можете делать разные вещи в зависимости от версии передаваемого типа вместо перегрузки для каждого типа.
Вы также можете иметь функции преобразования, которые принимают в MyType<Version>
и возвращают MyType<Version+1>
, общим образом, и даже рекурсируют их, чтобы иметь функцию ToNewest()
, которая возвращает последнюю версию типа из любой более старой версии (очень полезно для журналы, которые могли быть сохранены некоторое время назад, но должны быть обработаны с помощью новейшего на сегодняшний день инструмента).