En programación de computadoras , las plantillas variadas son plantillas que toman un número variable de argumentos.
Variadic plantillas son compatibles con C ++ (ya que el C ++ 11 estándar), y el lenguaje de programación D .
C ++
La característica de plantilla variadic de C ++ fue diseñada por Douglas Gregor y Jaakko Järvi [1] [2] y luego fue estandarizada en C ++ 11. Antes de C ++ 11, las plantillas (clases y funciones) solo podían aceptar un número fijo de argumentos, que debían especificarse cuando se declaraba por primera vez una plantilla. C ++ 11 permite que las definiciones de plantilla tomen un número arbitrario de argumentos de cualquier tipo.
template < typename ... Values > class tuple ; // toma cero o más argumentos
La clase de plantilla anterior tuple
tomará cualquier número de nombres de tipo como sus parámetros de plantilla. Aquí, una instancia de la clase de plantilla anterior se instancia con tres argumentos de tipo:
tupla < int , std :: vector < int > , std :: map < std :: string , std :: vector < int >>> some_instance_name ;
El número de argumentos puede ser cero, por lo que también funcionará.tuple<> some_instance_name;
Si la plantilla variadica solo debe permitir un número positivo de argumentos, entonces se puede usar esta definición:
template < typename Primero , typename ... Rest > class tuple ; // toma uno o más argumentos
Las plantillas variadas también pueden aplicarse a funciones, por lo que no solo proporcionan un complemento de tipo seguro para funciones variadas (como printf), sino que también permiten que una función llamada con una sintaxis similar a printf procese objetos no triviales.
plantilla < nombre de tipo ... Parámetros > vacío printf ( const std :: string y str_format , Params ... parámetros );
El operador de puntos suspensivos (...) tiene dos roles. Cuando aparece a la izquierda del nombre de un parámetro, declara un paquete de parámetros. Usando el paquete de parámetros, el usuario puede vincular cero o más argumentos a los parámetros de la plantilla variadic. Los paquetes de parámetros también se pueden utilizar para parámetros que no sean de tipo. Por el contrario, cuando el operador de puntos suspensivos aparece a la derecha de un argumento de llamada de plantilla o función, descomprime los paquetes de parámetros en argumentos separados, como args...
en el cuerpo de printf
abajo. En la práctica, el uso de un operador de puntos suspensivos en el código hace que toda la expresión que precede a los puntos suspensivos se repita para cada argumento posterior desempaquetado del paquete de argumentos, con las expresiones separadas por comas.
El uso de plantillas variadas suele ser recursivo. Los parámetros variadic en sí mismos no están fácilmente disponibles para la implementación de una función o clase. Por lo tanto, el mecanismo típico para definir algo como un printf
reemplazo variádico de C ++ 11 sería el siguiente:
// caso base void printf ( const char * s ) { while ( * s ) { if ( * s == '%' ) { if ( * ( s + 1 ) ! = '%' ) ++ s ; de lo contrario, lanza std :: runtime_error ( "cadena de formato no válida: argumentos faltantes" ); } std :: cout << * s ++ ; } }// plantilla recursiva < typename T , typename ... Args > void printf ( const char * s , T value , Args ... args ) { while ( * s ) { if ( * s == '%' ) { if ( * ( s + 1 ) ! = '%' ) { estándar :: cout << valor ; s + = 2 ; // solo funciona en cadenas de formato de 2 caracteres (% d,% f, etc.); falla con% 5.4f printf ( s , args ...); // llamado incluso cuando * s es 0 pero no hace nada en ese caso (e ignora los argumentos adicionales) return ; } ++ s ; } std :: cout << * s ++ ; } }
Esta es una plantilla recursiva. Observe que la versión de plantilla variadic de se printf
llama a sí misma, o (en el caso de que args...
esté vacía) llama al caso base.
No existe un mecanismo simple para iterar sobre los valores de la plantilla variadica. Sin embargo, hay varias formas de traducir el paquete de argumentos en un solo argumento que se puede evaluar por separado para cada parámetro. Por lo general, esto dependerá de la sobrecarga de la función o, si la función puede simplemente elegir un argumento a la vez, usando un marcador de expansión tonto:
plantilla < typename ... Args > pase anulado en línea ( Args && ...) {}
que se puede utilizar de la siguiente manera:
plantilla < typename ... Args > inline void expand ( Args && ... args ) { pass ( some_function ( args ) ... ); } expandir ( 42 , "respuesta" , verdadero );
que se expandirá a algo como:
pase ( some_function ( arg1 ), some_function ( arg2 ), some_function ( arg3 ) , etc ... );
El uso de esta función "pasar" es necesario, ya que la expansión del paquete de argumentos procede separando los argumentos de la llamada a la función por comas, que no son equivalentes al operador de coma. Por lo tanto, some_function(args)...;
nunca funcionará. Además, la solución anterior solo funcionará cuando el tipo de devolución some_function
no lo sea void
. Además, las some_function
llamadas se ejecutarán en un orden no especificado, porque el orden de evaluación de los argumentos de la función no está definido. Para evitar el orden no especificado, se pueden usar listas de inicializadores entre llaves, que garantizan un estricto orden de evaluación de izquierda a derecha. Una lista de inicializadores requiere un void
tipo de no retorno, pero el operador de coma puede usarse para ceder 1
para cada elemento de expansión.
struct pass { template < typename ... T > pass ( T ...) {} }; pasar {( alguna_función ( argumentos ), 1 ) ...};
En lugar de ejecutar una función, se puede especificar y ejecutar una expresión lambda en el lugar, lo que permite ejecutar secuencias arbitrarias de declaraciones en el lugar.
pass {([&] () {std :: cout << args << std :: endl;} (), 1) ...};
Sin embargo, en este ejemplo en particular, no es necesaria una función lambda. En su lugar, se puede usar una expresión más ordinaria:
pass {(std :: cout << args << std :: endl, 1) ...};
Otra forma es utilizar la sobrecarga con "versiones de terminación" de funciones. Esto es más universal, pero requiere un poco más de código y más esfuerzo para crearlo. Una función recibe un argumento de algún tipo y el paquete de argumentos, mientras que la otra no recibe ninguno. (Si ambos tuvieran la misma lista de parámetros iniciales, la llamada sería ambigua: un paquete de parámetros variadic por sí solo no puede eliminar la ambigüedad de una llamada). Por ejemplo:
void func () {} // versión de terminaciónplantilla < typename Arg1 , typename ... Args > void func ( const Arg1 & arg1 , const Args && ... args ) { proceso ( arg1 ); func ( argumentos ...); // nota: ¡arg1 no aparece aquí! }
Si args...
contiene al menos un argumento, se redirigirá a la segunda versión; un paquete de parámetros puede estar vacío, en cuyo caso simplemente se redirigirá a la versión de terminación, que no hará nada.
Las plantillas variables también se pueden usar en una especificación de excepción, una lista de clases base o la lista de inicialización de un constructor. Por ejemplo, una clase puede especificar lo siguiente:
plantilla < typename ... BaseClasses > class ClassName : public BaseClasses ... { public : ClassName ( BaseClasses && ... base_classes ) : BaseClasses ( base_classes ) ... {} };
El operador de descomprimir replicará los tipos para las clases base de ClassName
, de modo que esta clase se derivará de cada uno de los tipos pasados. Además, el constructor debe tomar una referencia a cada clase base, para inicializar las clases base de ClassName
.
Con respecto a las plantillas de funciones, los parámetros variadic se pueden reenviar. Cuando se combina con referencias universales (ver arriba), esto permite un reenvío perfecto:
template < typename TypeToConstruct > struct SharedPtrAllocator { template < typename ... Args > std :: shared_ptr < TypeToConstruct > construct_with_shared_ptr ( Args && ... params ) { return std :: shared_ptr < TypeToConstruct > ( nuevo TypeToConstruct ( std :: forward < Args > ( params ) ...)); } };
Esto descomprime la lista de argumentos en el constructor de TypeToConstruct. La std::forward
sintaxis reenvía perfectamente los argumentos como sus tipos adecuados, incluso con respecto a su valor, al constructor. El operador de desempaquetado propagará la sintaxis de reenvío a cada parámetro. Esta función de fábrica en particular envuelve automáticamente la memoria asignada en std::shared_ptr
un grado de seguridad con respecto a las pérdidas de memoria.
Además, el número de argumentos en un paquete de parámetros de plantilla se puede determinar de la siguiente manera:
plantilla < typename ... Args > struct SomeStruct { static const int size = sizeof ... ( Args ); };
La expresión SomeStruct
dará 2, mientras SomeStruct<>::size
que dará 0.
D
Definición
La definición de plantillas variadas en D es similar a su contraparte de C ++:
template VariadicTemplate ( Args ...) { / * Body * / }
Asimismo, cualquier argumento puede preceder a la lista de argumentos:
template VariadicTemplate ( T , valor de cadena , símbolo de alias , Args ...) { / * Body * / }
Uso básico
Los argumentos variádicos son muy similares a la matriz constante en su uso. Se pueden iterar, acceder a ellos mediante un índice, tener una length
propiedad y se pueden dividir . Las operaciones se interpretan en tiempo de compilación, lo que significa que los operandos no pueden ser valores en tiempo de ejecución (como parámetros de función).
Todo lo que se conoce en tiempo de compilación se puede pasar como argumentos variados. Hace argumentos variados similares a los argumentos de alias de plantilla , pero más poderosos, ya que también aceptan tipos básicos (char, short, int ...).
Aquí hay un ejemplo que imprime la representación de cadena de los parámetros variadic. StringOf
y StringOf2
producir resultados iguales.
static int s_int ;estructura ficticia {}void main () { pragma ( msg , StringOf ! ( "Hola mundo" , uint , Dummy , 42 , s_int )); pragma ( msg , StringOf2 ! ( "Hola mundo" , uint , Dummy , 42 , s_int )); }template StringOf ( Args ...) { enumeración StringOf = Args [ 0 ]. stringof ~ StringOf ! ( Args [ 1 .. $]); }template StringOf () { enumeración StringOf = "" ; }template StringOf2 ( Args ...) { static if ( Args . length == 0 ) enum StringOf2 = "" ; else enumeración StringOf2 = Args [ 0 ]. stringof ~ StringOf2 ! ( Args [ 1 .. $]); }
Salidas:
"Hola mundo" uintDummy42s_int"Hola mundo" uintDummy42s_int
AliasSeq
Las plantillas variadas se utilizan a menudo para crear una secuencia de alias, denominada AliasSeq . La definición de AliasSeq es realmente muy sencilla:
alias AliasSeq ( Args ...) = Args ;
Esta estructura permite manipular una lista de argumentos variados que se expandirán automáticamente. Los argumentos deben ser símbolos o valores conocidos en el momento de la compilación. Esto incluye valores, tipos, funciones o incluso plantillas no especializadas. Esto permite cualquier operación que esperaría:
import std . meta ;void main () { // Nota: AliasSeq no se puede modificar y un alias no se puede recuperar, por lo que necesitaremos definir nuevos nombres para nuestras modificaciones. números de alias = AliasSeq ! ( 1 , 2 , 3 , 4 , 5 , 6 ); // Alias de corte lastHalf = numeros [$ / 2 .. $]; estática assert ( lastHalf == AliasSeq (! 4 , 5 , 6 )); // AliasSeq expansión auto alias dígitos = AliasSeq (! 0 , números , 7 , 8 , 9 ); estática assert ( dígitos == AliasSeq (! 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 )); // std.meta proporciona plantillas para trabajar con AliasSeq, como anySatisfy, allSatisfy, staticMap y Filter. alias evenNumbers = Filter ! ( isEven , digits ); estática assert ( evenNumbers == AliasSeq (! 0 , 2 , 4 , 6 , 8 )); } plantilla isEven ( int número ) { enum isEven = ( 0 == ( número % 2 )); }
Ver también
Para artículos sobre construcciones variadas que no sean plantillas
Referencias
- ^ Douglas Gregor & Jaakko Järvi (febrero de 2008). "Plantillas Variadic para C ++ 0x" . Revista de tecnología de objetos . págs. 31–51.
- ^ Douglas Gregor; Jaakko Järvi y Gary Powell. (Febrero de 2004). "Plantillas Variadic. Número N1603 = 04-0043 en el envío de correos del Comité de Normas ISO C ++ antes de Sydney".