¿Cuáles son algunos usos de los parámetros de plantilla de plantilla?


He visto algunos ejemplos de C++ usando parámetros de plantilla de plantilla (es decir, plantillas que toman plantillas como parámetros) para hacer el diseño de clases basado en políticas. ¿Qué otros usos tiene esta técnica?

Author: Rakete1111, 2008-10-18

9 answers

Creo que necesita usar la sintaxis de plantilla de plantilla para pasar un parámetro cuyo tipo es una plantilla dependiente de otra plantilla como esta:

template <template<class> class H, class S>
void f(const H<S> &value) {
}

Aquí, H es una plantilla, pero quería que esta función se ocupara de todas las especializaciones de H.

NOTA: He estado programando c++ durante muchos años y solo he necesitado esto una vez. Me parece que es una característica rara vez se necesita (por supuesto útil cuando lo necesita!).

He estado tratando de pensar en lo bueno ejemplos, y para ser honesto, la mayoría de las veces esto no es necesario, pero inventemos un ejemplo. Vamos a fingir que std::vector no tiene un typedef value_type.

Entonces, ¿cómo escribiría una función que puede crear variables del tipo correcto para los elementos vectores? Esto funcionaría.

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;
}

NOTA : we std::vector tiene dos parámetros de plantilla, type y allocator, por lo que tuvimos que aceptar ambos. Afortunadamente, debido a la deducción de tipo, no tendremos que escribir el tipo exacto explícitamente.

Que puedes usar así:{[12]]}

f<std::vector, int>(v); // v is of type std::vector<int> using any allocator

O mejor aún, podemos usar:

f(v); // everything is deduced, f can deal with a vector of any type!

ACTUALIZACIÓN : Incluso este ejemplo artificial, aunque ilustrativo, ya no es un ejemplo sorprendente debido a que c++11 introduce auto. Ahora la misma función se puede escribir como:

template <class Cont>
void f(Cont &v) {

    auto temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

Que es como preferiría escribir este tipo de código.

 155
Author: Evan Teran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-29 23:03:40

En realidad, usecase para los parámetros de plantilla de plantilla es bastante obvio. Una vez que sepa que C++ stdlib tiene un enorme agujero de no definir operadores de salida de flujo para tipos de contenedores estándar, procedería a escribir algo como:

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;
}

Entonces descubrirías que el código para vector es el mismo, para forward_list es el mismo, en realidad, incluso para multitud de tipos de mapas sigue siendo el mismo. Esas clases de plantillas no tienen nada en común, excepto por meta-interfaz / protocolo, y el uso de plantilla parámetro de plantilla permite capturar la comunidad en todos ellos. Sin embargo, antes de escribir una plantilla, vale la pena verificar una referencia para recordar que los contenedores de secuencia aceptan 2 argumentos de plantilla: para el tipo de valor y el asignador. Mientras que el asignador está predeterminado, todavía debemos tener en cuenta su existencia en nuestro operador de plantilla

template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...

Voila, que funcionará automáticamente para todos los contenedores de secuencia presentes y futuros que se adhieran a el protocolo estándar. Para agregar mapas a la mezcla, echaría un vistazo a reference para tener en cuenta que aceptan 4 parámetros de plantilla, por lo que necesitaríamos otra versión del operador

Por cierto, con C+11 que permite plantillas variádicas (y por lo tanto debe permitir plantilla de plantilla variádica args), sería posible tener un solo operador

#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;
}

Salida

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 
 135
Author: pfalcon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-17 22:40:51

Aquí hay un ejemplo simple tomado de 'Modern C++ Design-Generic Programming and Design Patterns Applied' de Andrei Alexandrescu:

Utiliza clases con parámetros de plantilla para implementar el patrón de política:

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};

Él explica: Normalmente, la clase host ya conoce, o puede deducir fácilmente, el argumento de plantilla de la clase policy. En el ejemplo anterior, WidgetManager siempre administra objetos de tipo Widget, por lo que requiere que el usuario especificar widget de nuevo en la instanciación de CreationPolicy es redundante y potencialmente dangerous.In en este caso, el código de biblioteca puede usar parámetros de plantilla para especificar directivas.

El efecto es que el código del cliente puede usar 'WidgetManager' de una manera más elegante:

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;

En lugar de la forma más engorrosa y propensa a errores que una definición que carezca de argumentos de plantilla habría requerido:

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
 57
Author: yoav.aviram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-18 11:03:51

Aquí hay otro ejemplo práctico de mi biblioteca de red neuronal convolucional CUDA. Tengo la siguiente plantilla de clase:

template <class T> class Tensor

Que en realidad implementa la manipulación de matrices n-dimensionales. También hay una plantilla de clase secundaria:

template <class T> class TensorGPU : public Tensor<T>

Que implementa la misma funcionalidad pero en GPU. Ambas plantillas pueden trabajar con todos los tipos básicos, como float, double, int, etc Y también tengo una plantilla de clase (simplificada):

template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
    TT<T> weights;
    TT<T> inputs;
    TT<int> connection_matrix;
}

La razón aquí para tener plantilla la sintaxis de plantilla se debe a que puedo declarar la implementación de la clase

class CLayerCuda: public CLayerT<TensorGPU, float>

Que tendrá tanto pesos como entradas de tipo float y en GPU, pero connection_matrix siempre será int, ya sea en CPU (especificando TT = Tensor) o en GPU (especificando TT=TensorGPU).

 18
Author: Mikhail Sirotenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 11:40:58

Supongamos que está utilizando CRTP para proporcionar una "interfaz" para un conjunto de plantillas secundarias; y tanto el padre como el hijo son paramétricos en otros argumentos de plantilla:

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;

Tenga en cuenta la duplicación de 'int', que en realidad es el mismo parámetro de tipo especificado para ambas plantillas. Puede utilizar una plantilla de plantilla DERIVADA para evitar esta duplicación:

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;

Tenga en cuenta que está eliminando directamente proporcionar los otros parámetros de plantilla a la plantilla derivada; el "interface" todavía los recibe.

Esto también le permite construir typedefs en la "interfaz" que dependen de los parámetros de tipo, que serán accesibles desde la plantilla derivada.

El typedef anterior no funciona porque no se puede escribir a una plantilla no especificada. Esto funciona, sin embargo (y C++11 tiene soporte nativo para template typedefs):

template <typename VALUE>
struct derived_interface_type {
    typedef typename interface<derived, VALUE> type;
};

typedef typename derived_interface_type<int>::type derived_t;

Necesita un derived_interface_type para cada instanciación de la plantilla derivada desafortunadamente, a menos que haya otra truco que aún no he aprendido.

 9
Author: Mark McKenna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-11 15:49:07

En la solución con plantillas variádicas proporcionadas por pfalcon, me resultó difícil especializar realmente el operador de ostream para std::map debido a la naturaleza codiciosa de la especialización variádica. Aquí hay una ligera revisión que funcionó para mí:

#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;
}
 4
Author: Kuberan Naganathan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-19 01:52:44

Esto es lo que me encontré:

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)>>>>>>>>
{

};

Se puede resolver a:

template<class A>
class B
{
  A& a;
};

template< template<class> class B>
class A
{
  B<A> b;
};

class AInstance : A<B> //happy
{

};

O (código de trabajo):

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();
}
 4
Author: Cookie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-05 23:08:11

Aquí hay uno generalizado de algo que acabo de usar. Lo estoy publicando ya que es un muy ejemplo simple, y demuestra un caso de uso práctico junto con argumentos predeterminados:

#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;
};
 2
Author: imallett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-19 23:13:57

Mejora la legibilidad de su código, proporciona seguridad de tipo adicional y ahorra algunos esfuerzos del compilador.

Digamos que desea imprimir cada elemento de un contenedor, puede usar el siguiente código sin plantilla parámetro de plantilla

template <typename T> void print_container(const T& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

O con el parámetro template template

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';
}

Supongamos que pasa un entero digamos print_container(3). Para el primer caso, la plantilla será instanciada por el compilador que se quejará del uso de c en el bucle for, el último lo hará no instanciar la plantilla en absoluto, ya que no se puede encontrar ningún tipo coincidente.

En términos generales, si su clase/función de plantilla está diseñada para manejar la clase de plantilla como parámetro de plantilla, es mejor dejarlo claro.

 0
Author: colin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-30 18:35:21