Я видел несколько примеров C ++, использующих параметры шаблонов шаблонов (то есть шаблоны, которые принимают шаблоны в качестве параметров) для разработки классов на основе политик. Какие другие применения у этой техники?
c++
templates
template-templates
Ферруччо
источник
источник
Ответы:
Я думаю, вам нужно использовать синтаксис шаблона шаблона для передачи параметра, тип которого является шаблоном, зависящим от другого шаблона, например:
Вот
H
шаблон, но я хотел, чтобы эта функция имела дело со всеми специализациямиH
.ПРИМЕЧАНИЕ : я программировал на С ++ много лет, и мне это понадобилось только один раз. Я считаю, что это редко необходимая функция (конечно, удобная, когда она вам нужна!).
Я пытался придумать хорошие примеры, и, честно говоря, большую часть времени в этом нет необходимости, но давайте создадим пример. Давайте притворимся, что
std::vector
не имеетtypedef value_type
.Итак, как бы вы написали функцию, которая может создавать переменные правильного типа для элементов векторов? Это будет работать.
ПРИМЕЧАНИЕ :
std::vector
имеет два параметра шаблона, тип и распределитель, поэтому нам пришлось принять оба из них. К счастью, из-за вывода типов нам не нужно явно выписывать точный тип.который вы можете использовать так:
или еще лучше, мы можем просто использовать:
ОБНОВЛЕНИЕ : Даже этот надуманный пример, хотя и иллюстративный, больше не является удивительным примером из-за введения c ++ 11
auto
. Теперь ту же функцию можно записать так:именно так я бы предпочел написать этот тип кода.
источник
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
и нетf<vector<int>>
.f<vector,int>
значитf<ATemplate,AType>
,f<vector<int>>
значитf<AType>
На самом деле, сценарий использования параметров шаблона шаблона довольно очевиден. Как только вы узнаете, что в C ++ stdlib есть дыра, не позволяющая определять операторы потокового вывода для стандартных типов контейнеров, вы начинаете писать что-то вроде:
Тогда вы поймете, что код для вектора такой же, для forward_list одинаков, на самом деле, даже для множества типов карт он все тот же. Эти классы шаблонов не имеют ничего общего, кроме мета-интерфейса / протокола, и использование параметра шаблона шаблона позволяет зафиксировать общность во всех из них. Прежде чем приступить к написанию шаблона, стоит проверить ссылку, чтобы напомнить, что контейнеры последовательности принимают 2 аргумента шаблона - для типа значения и распределителя. Пока по умолчанию используется allocator, мы все равно должны учитывать его существование в нашем шаблонном операторе <<:
Вуаля, это будет работать автоматически для всех существующих и будущих контейнеров последовательностей, придерживающихся стандартного протокола. Чтобы добавить карты в микс, нужно взглянуть на ссылку, чтобы заметить, что они принимают 4 параметра шаблона, поэтому нам понадобится другая версия оператора << выше с 4-аргументным шаблоном param. Мы также увидим, что std: pair пытается отображаться с помощью оператора 2-arg << для типов последовательностей, которые мы определили ранее, поэтому мы предоставим специализацию только для std :: pair.
Между прочим, с C + 11, который допускает шаблоны с переменным числом (и, следовательно, должен разрешать аргументы шаблона шаблона с переменным числом аргументов), можно было бы иметь один оператор <<, чтобы управлять ими всеми. Например:
Вывод
источник
__PRETTY_FUNCTION__
, который, помимо прочего, сообщает об описании параметров шаблона в виде простого текста. Clang делает это также. Иногда очень удобная функция (как вы можете видеть).Вот простой пример, взятый из « Андрея Александреску»: «Современный дизайн C ++ - универсальные шаблоны программирования и проектирования» :
Он использует классы с параметрами шаблона шаблона для реализации шаблона политики:
Он объясняет: как правило, хост-класс уже знает или может легко вывести аргумент шаблона класса политики. В приведенном выше примере WidgetManager всегда управляет объектами типа Widget, поэтому требование пользователя снова указывать Widget в экземпляре CreationPolicy является избыточным и потенциально опасным. В этом случае код библиотеки может использовать параметры шаблона шаблона для определения политик.
В результате клиентский код может использовать WidgetManager более элегантным способом:
Вместо более обременительного и подверженного ошибкам способа, который потребовалось бы для определения, в котором отсутствуют аргументы шаблона:
источник
Вот еще один практический пример из моей библиотеки CUDA Convolutional нейронных сетей . У меня есть следующий шаблон класса:
который фактически реализует манипулирование n-мерными матрицами. Также есть шаблон дочернего класса:
который реализует ту же функциональность, но в графическом процессоре. Оба шаблона могут работать со всеми основными типами, такими как float, double, int и т. Д. И у меня также есть шаблон класса (упрощенно):
Причина использования синтаксиса шаблона шаблона заключается в том, что я могу объявить реализацию класса
который будет иметь как весовые коэффициенты, так и входные данные типа float и для графического процессора, но connection_matrix всегда будет int, либо на процессоре (указав TT = Tensor), либо на GPU (указав TT = TensorGPU).
источник
Допустим, вы используете CRTP для предоставления «интерфейса» для набора дочерних шаблонов; и родитель и потомок являются параметрическими в других аргументах шаблона:
Обратите внимание на дублирование int, который на самом деле является одним и тем же параметром типа, заданным для обоих шаблонов. Вы можете использовать шаблон шаблона для DERIVED, чтобы избежать этого дублирования:
Обратите внимание, что вы исключаете непосредственное предоставление других параметров шаблона в производный шаблон; «интерфейс» по-прежнему получает их.
Это также позволяет вам создавать typedefs в «интерфейсе», которые зависят от параметров типа, которые будут доступны из производного шаблона.
Приведенный выше typedef не работает, потому что вы не можете использовать typedef для неопределенного шаблона. Это работает, однако (и C ++ 11 имеет встроенную поддержку шаблонов typedefs):
К сожалению, вам нужен один производный_интерфейс_типа для каждого экземпляра производного шаблона, если только нет другого трюка, который я еще не изучил.
источник
derived
может использоваться без аргументов шаблона, то есть строкиtypedef typename interface<derived, VALUE> type;
template <typename>
. В некотором смысле вы можете думать о параметрах шаблона как о «метатипе»; нормальный метатип для параметра шаблонаtypename
означает, что он должен быть заполнен обычным типом; тоtemplate
метатип средство он должен быть заполнено ссылкой на шаблон.derived
определяет шаблон, который принимает одинtypename
метатипизированный параметр, поэтому он соответствует требованиям и на него можно ссылаться здесь. Есть смысл?typedef
. Кроме того, вы можете избежать дублированияint
в вашем первом примере, используя стандартную конструкцию, например,value_type
в типе DERIVED.typedef
проблему из блока 2. Но пункт 2 действителен, я думаю ... да, возможно, это был бы более простой способ сделать то же самое.Вот с чем я столкнулся:
Может быть решено для:
или (рабочий код):
источник
В решении с вариадическими шаблонами, предоставленными pfalcon, мне было трудно фактически специализировать ostream-оператор для std :: map из-за жадного характера вариационной специализации. Вот небольшая ревизия, которая сработала для меня:
источник
Вот один обобщенный из того, что я только что использовал. Я публикую его, так как это очень простой пример, и он демонстрирует практический пример использования вместе с аргументами по умолчанию:
источник
Это улучшает читабельность вашего кода, обеспечивает дополнительную безопасность типов и экономит некоторые усилия компилятора.
Скажем, вы хотите напечатать каждый элемент контейнера, вы можете использовать следующий код без параметра шаблона шаблона
или с параметром шаблона шаблона
Предположим, вы передаете целочисленное слово
print_container(3)
. В первом случае шаблон будет создан экземпляром компилятора, который будет жаловаться на использованиеc
в цикле for, последний не будет создавать экземпляр шаблона вообще, так как не может быть найден соответствующий тип.Вообще говоря, если ваш класс / функция шаблона предназначен для обработки класса шаблона в качестве параметра шаблона, лучше прояснить это.
источник
Я использую это для версионных типов.
Если у вас есть тип версионный через шаблон, например
MyType<version>
, вы можете написать функцию, в которой вы можете захватить номер версии:Таким образом, вы можете делать разные вещи в зависимости от версии передаваемого типа вместо перегрузки для каждого типа. Вы также можете иметь функции преобразования, которые принимают
MyType<Version>
и возвращаютMyType<Version+1>
, в общем, и даже рекурсивно их использовать, чтобы иметьToNewest()
функцию, которая возвращает последнюю версию типа из любой более старой версии (очень полезно для журналов, которые могли быть сохранены некоторое время назад). но должны быть обработаны с помощью новейшего инструмента сегодня).источник