Plantillas variadas o el poder de tres puntos

Plantillas variadas o el poder de tres puntos

Una plantilla variable es una plantilla que puede tener un número arbitrario de parámetros de plantilla. Esta característica puede parecerle mágica si la ve por primera vez. Entonces, permítanme desmitificar las plantillas variadas.

Quizás se pregunte si mi gráfico que muestra los temas sobre los que escribo incluye creación de instancias de plantilla. La razón es simple. Después de mi última publicación sobre "Creación de instancias de plantillas", uno de mis lectores alemanes (seudónimo Urfahraner Auge) hizo un comentario. Hay una diferencia importante entre la instanciación implícita y explícita de una plantilla que olvidé mencionar. El esta en lo correcto. La instanciación implícita de plantillas es perezosa, pero la instanciación explícita de plantillas es ansiosa.

Creación de instancias de plantillas perezosas versus ansiosas

La creación de instancias de plantillas es perezosa. Es decir, si no necesita una función miembro de una plantilla de clase, no se creará una instancia. Solo está disponible la declaración de la función miembro, pero no su definición. Esto funciona hasta ahora que puede usar código no válido en una función miembro. Por supuesto, la función miembro no debe llamarse.

// numberImplicitExplicit.cpp

#include <cmath>
#include <string>

template <typename T>
struct Number {
 int absValue() {
 return std::abs(val);
 }
 T val{};
};

// template class Number<std::string>; // (2)
// template int Number<std::string>::absValue(); // (3)

int main() {
 
 Number<std::string> numb;
 // numb.absValue(); // (1)
 
}

Si llama a la función miembro numb.absValue() (línea 1), obtienes lo que esperas. Un mensaje de error en tiempo de compilación básicamente dice que no hay sobrecarga std::abs para std::string disponible. Estas son las dos primeras líneas del mensaje de error detallado:

Tengo que explicar la instanciación de plantillas con mayor precisión: La instanciación implícita de plantillas es perezosa, pero la instanciación explícita de plantillas es ansiosa.

Cuando habilita la línea (2) (template class Number<std::string> ) e instanciado explícitamente la plantilla de clase Number o habilita la línea (3) (template int Number<std::string>::absValue( )) e instanciado explícitamente la función miembro absValue para std::string , obtiene un error en tiempo de compilación. Este error en tiempo de compilación es equivalente al error del compilador que invoca la función miembro absValue en la línea (1) (numb.absValue() ). Una vez más, aquí están las dos primeras líneas de los mensajes de error después de habilitar la línea (2) o la línea (3).

  • Línea (2) habilitada

  • Línea (3) habilitada

Una nota personal:

Estoy interesado en recibir comentarios sobre mis publicaciones. Me ayudan a escribir sobre el contenido que quieres escuchar. En particular, la comunidad alemana está muy comprometida.

Ahora, finalmente a algo completamente diferente:plantillas variadas.

Plantillas Variádicas

Una plantilla variable es una plantilla que puede tener un número arbitrario de parámetros de plantilla. Esta característica puede parecerle mágica si la ve por primera vez.

template <typename ... Args>
void variadicTemplate(Args ... args) { 
 . . . . // four dots
}

Los puntos suspensivos (... ) hace Args o args un llamado paquete de parámetros. Precisamente, Args es un paquete de parámetros de plantilla y args es un paquete de parámetros de función. Dos operaciones son posibles con los paquetes de parámetros. Se pueden empaquetar y desempacar. Si la elipse está a la izquierda de Args , el paquete de parámetros se empaquetará, si está a la derecha de Args , está desempaquetado. Debido a la deducción del argumento de la plantilla de función, el compilador puede derivar los argumentos de la plantilla.

Las plantillas Variadic se utilizan a menudo en la biblioteca de plantillas estándar y también en el lenguaje principal.

template <typename... Types> // (1)
class tuple; 

template <typename Callable, typename... Args > // (2)
explicit thread(Callable&& f, Args&&... args); 

template <typename Lockable1, typename Lockable2, typename... LockableN> // (3)
void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockn);

sizeof...(ParameterPack); // (4)

Los cuatro ejemplos del estándar C++11 usan plantillas variadas. Los tres primeros forman parte de la Biblioteca de plantillas estándar. Veamos qué puedo deducir de las declaraciones.

  1. std::tuple acepta un número arbitrario de tipos diferentes.
  2. std::thread le permite invocar un invocable con un número arbitrario de argumentos. El argumento puede tener diferentes tipos. Un invocable es algo que puede invocar, como una función, un objeto de función o una expresión lambda. La función std::thread toma su invocable y sus argumentos por referencia universal. Si necesita más detalles:ya escribí sobre la deducción de argumentos de plantilla y las referencias universales en mi publicación "Argumentos de plantilla".
  3. std::lock le permite bloquear un número arbitrario de tipos bloqueables en un paso atómico. Bloquear un tipo bloqueable en un paso atómico es trivial. En consecuencia, std::lock requiere al menos dos argumentos. Lockable se denomina requisito. Tipos compatibles con Lockable debe tener las funciones miembro lock , unlock y try_lock .
  4. El sizeof ... - el operador devuelve el número de elementos en el ParameterPack .

El sizeof... -operator parece ser especial porque el ParameterPack se usa en el lenguaje central. Permítanme escribir algunas palabras al respecto.

sizeof.. .-Operador

Gracias al sizeof ...-operador se puede utilizar para determinar directamente cuántos elementos contiene un paquete de parámetros. Los elementos no se evalúan.

// printSize.cpp

#include <iostream>

using namespace std::literals;

template <typename ... Args>
void printSize(Args&& ... args){
 std::cout << sizeof...(Args) << ' '; // (1)
 std::cout << sizeof...(args) << '\n'; // (2)
}

int main() {

 std::cout << '\n';

 printSize(); // (3)
 printSize("C string", "C++ string"s, 2011, true); // (4)

 std::cout << '\n';

}

El sizeof ..-operator le permite determinar el tamaño del paquete de parámetros de plantilla (1) y el paquete de parámetros de función (2) en tiempo de compilación. Lo aplico a un paquete de parámetros vacío (3) y a un paquete de parámetros que contiene cuatro elementos. El primer elemento es una cadena C y el segundo una cadena C++. Para usar el literal de cadena C++, debo incluir el espacio de nombres std::literals (5). C++14 admite literales de cadena C++.

¿Qué sigue?

En mi próxima publicación, profundizo más en las plantillas variádicas e introduzco el patrón funcional para evaluar una plantilla variádica. Además, presento la función de fábrica perfecta y salto de C++11 a C++17:doblar expresión en C++17.