Каковы некоторые параметры шаблона шаблона?

Я видел несколько примеров С++ с использованием параметров шаблона шаблона (то есть шаблонов, которые принимают шаблоны в качестве параметров) для разработки дизайна на основе политик. Какое другое использование имеет этот метод?

Ответ 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(), которая возвращает последнюю версию типа из любой более старой версии (очень полезно для журналы, которые могли быть сохранены некоторое время назад, но должны быть обработаны с помощью новейшего на сегодняшний день инструмента).