He visto algunos ejemplos de C ++ usando parámetros de plantilla de plantilla (es decir, plantillas que toman plantillas como parámetros) para hacer un diseño de clase basado en políticas. ¿Qué otros usos tiene esta técnica?
c++
templates
template-templates
Ferruccio
fuente
fuente
Respuestas:
Creo que debe usar la sintaxis de plantilla de plantilla para pasar un parámetro cuyo tipo es una plantilla dependiente de otra plantilla como esta:
Aquí
H
hay una plantilla, pero quería que esta función se ocupe de todas las especializaciones deH
.NOTA : He estado programando c ++ durante muchos años y solo he necesitado esto una vez. Me parece que es una característica que rara vez se necesita (¡por supuesto, útil cuando la necesita!).
He estado tratando de pensar en buenos ejemplos, y para ser honesto, la mayoría de las veces esto no es necesario, pero inventemos un ejemplo. Supongamos que
std::vector
no tiene untypedef value_type
.Entonces, ¿cómo escribiría una función que pueda crear variables del tipo correcto para los elementos de los vectores? Esto funcionaria.
NOTA :
std::vector
tiene dos parámetros de plantilla, tipo y asignador, por lo que tuvimos que aceptar ambos. Afortunadamente, debido a la deducción de tipo, no necesitaremos escribir el tipo exacto explícitamente.que puedes usar así:
o mejor aún, podemos usar:
ACTUALIZACIÓN : Incluso este ejemplo artificial, aunque ilustrativo, ya no es un ejemplo sorprendente debido a la introducción de c ++ 11
auto
. Ahora la misma función se puede escribir como:así es como preferiría escribir este tipo de código.
fuente
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
y nof<vector<int>>
.f<vector,int>
significaf<ATemplate,AType>
,f<vector<int>>
significaf<AType>
En realidad, el caso de uso para los parámetros de plantilla de plantilla es bastante obvio. Una vez que sepa que C ++ stdlib tiene un gran vacío de no definir operadores de salida de flujo para tipos de contenedor estándar, procederá a escribir algo como:
Entonces descubrirías que el código para el 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 plantilla no tienen nada en común, excepto la metainterfaz / protocolo, y el uso de parámetros de plantilla de plantilla permite capturar la comunidad en todos ellos. Sin embargo, antes de proceder a 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. Si bien el asignador está predeterminado, aún debemos tener en cuenta su existencia en nuestro operador de plantilla <<:
Voila, que funcionará automáticamente para todos los contenedores de secuencia presentes y futuros que se adhieran al protocolo estándar. Para agregar mapas a la mezcla, sería necesario echar un vistazo a la referencia para notar que aceptan 4 parámetros de plantilla, por lo que necesitaríamos otra versión del operador << anterior con el parámetro de plantilla de plantilla de 4 argumentos. También veríamos que std: pair intenta representarse con el operador 2-arg << para los tipos de secuencia que definimos anteriormente, por lo que proporcionaríamos una especialización solo para std :: pair.
Por cierto, con C + 11 que permite plantillas variadas (y, por lo tanto, debería permitir argumentos de plantilla de plantilla variable), sería posible tener un solo operador << para gobernarlos a todos. Por ejemplo:
Salida
fuente
__PRETTY_FUNCTION__
, que, entre otras cosas, informa las descripciones de los parámetros de la plantilla en texto plano. el sonido metálico también lo hace. Una característica más útil a veces (como puede ver).Aquí hay un ejemplo simple tomado de 'Diseño moderno de C ++ - Programación genérica y patrones de diseño aplicados' por Andrei Alexandrescu:
Utiliza clases con parámetros de plantilla de plantilla para implementar el patrón de política:
Explica: Normalmente, la clase de host ya conoce, o puede deducir fácilmente, el argumento de plantilla de la clase de política. En el ejemplo anterior, WidgetManager siempre gestiona objetos de tipo Widget, por lo que requerir que el usuario especifique Widget nuevamente en la creación de instancias de CreationPolicy es redundante y potencialmente peligroso. En este caso, el código de la biblioteca puede usar parámetros de plantilla de plantilla para especificar políticas.
El efecto es que el código del cliente puede usar 'WidgetManager' de una manera más elegante:
En lugar de la forma más engorrosa y propensa a errores que una definición que carece de argumentos de plantilla de plantilla habría requerido:
fuente
Aquí hay otro ejemplo práctico de mi biblioteca de redes neuronales convolucionales CUDA . Tengo la siguiente plantilla de clase:
que en realidad implementa la manipulación de matrices n-dimensionales. También hay una plantilla de clase secundaria:
que implementa la misma funcionalidad pero en GPU. Ambas plantillas pueden funcionar con todos los tipos básicos, como float, double, int, etc. Y también tengo una plantilla de clase (simplificada):
La razón aquí para tener una sintaxis de plantilla es porque puedo declarar la implementación de la clase
que tendrá tanto pesos como entradas de tipo flotante y en GPU, pero connection_matrix siempre será int, ya sea en CPU (especificando TT = Tensor) o en GPU (especificando TT = TensorGPU).
fuente
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:
Tenga en cuenta la duplicación de 'int', que en realidad es el mismo parámetro de tipo especificado para ambas plantillas. Puede usar una plantilla de plantilla para DERIVADO para evitar esta duplicación:
Tenga en cuenta que está eliminando proporcionar directamente los otros parámetros de plantilla a la plantilla derivada ; la "interfaz" todavía los recibe.
Esto también le permite construir typedefs en la "interfaz" que dependen de los parámetros de tipo, a los que se podrá acceder desde la plantilla derivada.
El typedef anterior no funciona porque no puede escribir typedef en una plantilla no especificada. Sin embargo, esto funciona (y C ++ 11 tiene soporte nativo para plantillas typedefs):
Desafortunadamente, necesita un derivado_tipo_interfaz_por cada instancia de la plantilla derivada, a menos que haya otro truco que aún no haya aprendido.
fuente
derived
se puede usar la clase de plantilla sin sus argumentos de plantilla, es decir, la líneatypedef typename interface<derived, VALUE> type;
template <typename>
. En cierto sentido, puede pensar que los parámetros de la plantilla tienen un 'metatipo'; el metatipo normal para un parámetro de plantilla es lotypename
que significa que debe llenarse con un tipo regular; eltemplate
metatipo significa que debe llenarse con una referencia a una plantilla.derived
define una plantilla que acepta untypename
parámetro metatipado, por lo que se ajusta a la factura y se puede hacer referencia aquí. ¿Tener sentido?typedef
. Además, puede evitar el duplicadoint
en su primer ejemplo utilizando una construcción estándar como avalue_type
en el tipo DERIVADO.typedef
solucionar el problema desde el bloque 2. Pero creo que el punto 2 es válido ... sí, esa sería probablemente una forma más sencilla de hacer lo mismo.Esto es con lo que me encontré:
Se puede resolver a:
o (código de trabajo):
fuente
En la solución con plantillas variadas proporcionadas por pfalcon, me resultó difícil especializar el operador ostream para std :: map debido a la naturaleza codiciosa de la especialización variable. Aquí hay una pequeña revisión que funcionó para mí:
fuente
Aquí hay uno generalizado de algo que acabo de usar. Lo estoy publicando ya que es un ejemplo muy simple y demuestra un caso de uso práctico junto con argumentos predeterminados:
fuente
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 parámetro de plantilla de plantilla
o con el parámetro de plantilla de plantilla
Suponga que pasa en un número entero
print_container(3)
. Para el primer caso, la plantilla será instanciada por el compilador que se quejará sobre el uso dec
en el bucle for, el último no instanciará la plantilla en absoluto ya que no se puede encontrar ningún tipo de coincidencia.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.
fuente
Lo uso para tipos versionados.
Si tiene un tipo versionado a través de una plantilla como
MyType<version>
, puede escribir una función en la que pueda capturar el número de versión:Por lo tanto, puede hacer diferentes cosas dependiendo de la versión del tipo que se pasa en lugar de tener una sobrecarga para cada tipo. También puede tener funciones de conversión que aceptan
MyType<Version>
y devuelvenMyType<Version+1>
, de forma genérica, e incluso las repiten para tener unaToNewest()
función que devuelve la última versión de un tipo de cualquier versión anterior (muy útil para registros que podrían haberse almacenado hace un tiempo pero debe procesarse con la herramienta más nueva de hoy).fuente