Una plantilla variable o una plantilla con un número variable de argumentos ( ing. Variadic Template ) en programación es una plantilla con un número previamente desconocido de argumentos que forman uno o más de los llamados paquetes de parámetros .
La plantilla variádica le permite utilizar la parametrización de tipo donde desea operar en un número arbitrario de argumentos, cada uno de los cuales tiene un tipo arbitrario [1] . Puede ser muy útil en situaciones en las que el escenario de comportamiento de la plantilla se puede generalizar a una cantidad desconocida de datos recibidos [2] .
Las plantillas de variables son compatibles con C++ (desde el estándar C++11 ) y D.
La plantilla variable en C++ (también conocida como paquete de parámetros ) fue desarrollada por Douglas Gregor y Jaakko Järvi [3] [4] y luego se estandarizó en C++11. Antes de C++ 11, las plantillas (de clases y funciones) solo podían aceptar un número fijo de argumentos, que tenían que definirse cuando se declaraba la plantilla por primera vez.
Sintaxis de plantilla variable:
template < typename ... Values > class tuple ;La plantilla de clase de tupla anterior puede tomar cualquier número de parámetros de entrada. Por ejemplo, se crea una instancia de la clase de plantilla anterior con tres argumentos:
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 tuple<> some_instance_name;que también funcionará. Si no desea permitir que se creen objetos de plantilla de variante con cero argumentos, puede usar la siguiente declaración:
template < typename Primero , typename ... Resto > class tuple ;Las plantillas variables también se pueden aplicar a las funciones.
template < typename ... Params > void printf ( const std :: string & str_format , Params ... parámetros );El operador de puntos suspensivos (...) juega dos roles. Cuando aparece a la izquierda del nombre de un parámetro de función, declara un conjunto de parámetros. Cuando el operador de puntos suspensivos está a la derecha de un argumento de llamada de plantilla o función, descomprime los parámetros en argumentos separados, como args...en el cuerpo a printfcontinuación.
void printf ( const char * s ) { mientras ( * s ) { si ( * s == '%' ) { si ( * ( s + 1 ) == '%' ) { ++ s ; } más { throw std :: runtime_error ( "formato de cadena incorrecto: faltan argumentos" ); } } estándar :: cout << * s ++ ; } } plantilla < nombre de tipo T , nombre de tipo ... Args > void printf ( const char * s , valor T , argumentos ... argumentos ) { mientras ( * s ) { si ( * s == '%' ) { si ( * ( s + 1 ) == '%' ) { ++ s ; } más { std :: cout << valor ; s += 2 ; // solo funciona para dos especificadores de formato de caracteres (p. ej., %d, %f ). No funcionará con %5.4f printf ( s , argumentos ...); // la llamada ocurre incluso cuando *s == 0 para detectar argumentos redundantes volver ; } } estándar :: cout << * s ++ ; } }Este es un patrón recursivo. Tenga en cuenta que esta versión variante de plantilla de printf se llama a sí misma o (si args... está vacío) la variante predeterminada.
No existe un mecanismo simple para enumerar los valores de las plantillas variables. Hay varias formas de convertir una lista de argumentos en un solo argumento. Esto generalmente se implementa usando la sobrecarga de funciones, o si la función solo puede tomar un argumento a la vez, usando un marcador de extensión simple:
plantilla < nombre de tipo ... Args > pase vacío en línea ( Args && ...) {}esta plantilla se puede utilizar así:
template < typename ... Args > expansión de vacío en línea ( Args && ... args ) { pasar ( alguna_función ( argumentos )... ); } expandir ( 42 , "respuesta" , verdadero );y se convertirá en algo como:
pass ( alguna_funcion ( arg1 ) , alguna_funcion ( arg2 ) , alguna_funcion ( arg3 ) etc ... ) ; _El uso de la función "pasar" es necesario porque el desempaquetado de argumentos se produce al separar los argumentos de la función separados por comas, que no son equivalentes al operador coma. Por lo tanto some_function(args)...; nunca funcionará. Además, la solución anterior solo funcionará cuando el tipo de retorno de some_function no sea void . Además, las llamadas a alguna_función se ejecutarán en un orden arbitrario, porque el orden en que se evalúan los argumentos de la función no está definido. Para evitar un orden arbitrario, se puede usar una lista de inicialización entre paréntesis para garantizar que se mantenga la secuencia de izquierda a derecha.
paso de estructura { plantilla < nombre de tipo ... T > pase ( T ...) {} }; pasar {( alguna_función ( argumentos ), 1 )...};En lugar de llamar a una función, puede crear una expresión lambda y ejecutarla en su lugar.
pasar{([&]{ std::cout << argumentos << std::endl; }(), 1)...};Sin embargo, en este ejemplo particular, no se requiere la función lambda. Puedes usar expresiones regulares:
pasar{(std::cout << argumentos << std::endl, 1)...};Otra forma es usar la sobrecarga de funciones. Esta es una forma más versátil, pero requiere un poco más de líneas de código y esfuerzo. Una función toma un argumento de algún tipo y un conjunto de argumentos, mientras que la otra (terminal) no toma nada. Si ambas funciones tienen la misma lista de parámetros iniciales, la llamada será ambigua. Por ejemplo:
función vacía () {} // versión final plantilla < nombre de tipo Arg1 , nombre de tipo ... Args > función vacía ( const Arg1 & arg1 , const Args & ... args ) { proceso ( arg1 ); func ( argumentos ...); // nota: ¡arg1 no aparece aquí! }Las plantillas de variables también se pueden usar en excepciones, listas de clases base o listas de inicialización de constructores. Por ejemplo, una clase podría heredar lo siguiente:
template < typename ... BaseClasses > class ClassName : public BaseClasses ... { público : ClassName ( BaseClasses && ... base_classes ) : BaseClasses ( base_classes )... {} };El operador de desempaquetado sustituirá las clases base por la clase derivada ClassName; por lo tanto, esta clase heredará todas las clases que se le pasan. Además, el constructor debe aceptar una referencia a cada clase base.
En cuanto a las plantillas de funciones variádicas , los parámetros se pueden reenviar. Combinado con el enlace universal (ver arriba), esto permite un excelente reenvío:
plantilla < nombre de tipo TypeToConstruct > struct SharedPtrAllocator { template < typename ... Args > std :: shared_ptr < TypeToConstruct > construct_with_shared_ptr ( Args && ... params ) { return std :: shared_ptr < TypeToConstruct > ( new TypeToConstruct ( std :: forward < Args > ( params )...)); } };Este código desempaqueta la lista de argumentos en un constructor TypeToConstruct. La sintaxis std::forward<Args>(params)pasa los argumentos, así como sus tipos, incluso con la característica rvalue, al constructor. Esta función de fábrica asigna automáticamente la memoria asignada para std::shared_ptrevitar pérdidas de memoria.
Además, el número de parámetros en una plantilla se puede definir de la siguiente manera:
template < typename ... Args > struct SomeStruct { static const int size = sizeof ...( Args ); };La expresión SomeStruct<Type1, Type2>::sizedevolverá 2 y la expresión SomeStruct<>::sizedevolverá 0.
Ejemplo de función de suma: doble suma ( doble x ) { devuelve x ; } plantilla < clase ... Argumentos > doble suma ( doble x , Args ... args ) { devuelve x + suma ( argumentos ...); }