Reglas de Inferencia y Deducción de Tipos en C++
Inferencia de Tipos
La palabra clave auto
auto solo puede utilizarse en inicializaciones, sirve para inferir tipos automáticamente y funciona en tiempo de compilación.
Es importante destacar que auto solo infiere el tipo del valor, eliminando修饰adores como const y referencias. Por supuesto, estos modificadores pueden combinarse con auto.
A partir de C++14, auto puede utilizarse en parámetros de funciones lambda, y desde C++20 esta característica se extendió a funciones normales y valores de retorno de plantillas. Consideremos el siguiente ejemplo:
auto sumar14 = [](auto x, auto y) -> int {
return x + y;
}
auto sumar20(auto x, auto y) {
return x + y;
//El compilador genera automáticamente un conjunto de plantillas.
int a = 5; // tipo int
int b = 6; // tipo int
std::cout << sumar14(a, b) << std::endl;
std::cout << sumar20(a, b) << std::endl;
[!WARNING]
autotodavía no puede utilizarse para deducir tipos de arrays:auto arr_auto[10] = {arr}; // Error, no se puede deducir el tipo de elementos del array 2.6.auto.cpp:30:19: error: 'arr_auto' declarado como array de 'auto' auto arr_auto[10] = {arr};Tampoco se permite usarlo directamente en la generación de plantillas, como por ejemplo
vector<auto>.
La palabra clave decltype
La palabra clave decltype apareció para solucionar las limitaciones de auto, que solo puede deducir tipos de variables. decltype se utiliza para manejar expresiones.
Por lo tanto, decltype puede deducir categorías de valor como const y referencias, preservando estrictamente la categoría de la expresión en lugar de solo el tipo.
Un uso común es el siguiente:
int valor1 = 10;
double valor2 = 100.1;
decltype(valor1 + valor2) resultado = valor1 + valor2;
//decltype puede deducir el tipo resultante de la expresión, aunque aquí también podría usarse auto.
C++11 también introdujo lo que se conoce como tipo de retorno trailing (tipo de retorno al final), utilizando la palabra clave auto para postergar el tipo de retorno e inferir el tipo de retorno de la plantilla:
template
auto sumar2(T x, U y) -> decltype(x + y) {
return x + y;
}
A partir de C++14, los funciones normales pueden tener deducción de retorno, por lo que la siguiente写法变得合法:
template
auto sumar3(T x, U y) {
return x + y;
}
La expresión decltype(auto)
decltype(auto) analiza el tipo y la categoría de valor de la expresión como lo hace decltype, pero sintácticamente no requiere especificar la expresión explícitamente como auto.
Reglas de Deducción de Tipos
Las reglas de deducción de tipos son bastante intuitivas, lo cual es precisamente su objetivo, así que no es necesario memorizarlas, solo comprenderlas.
Deducción de Tipos en Plantillas
La deducción de tipos de auto se basa en plantillas, así que veamos las reglas de las plantillas.
Supongamos un caso básico:
template
f(ParamType param);
f(expresion);
La deducción de ParamType y T es diferente, simplemente porque ParamType puede llevar varios modificadores como punteros o const.
Aquí hay tres casos:
ParamTypees un puntero o referencia, pero no una referencia universalParamTypees una referencia universalParamTypeno es ni puntero ni referencia
Primer Caso
Si expr es una referencia, se ignora la parte de referencia, y luego se hace pattern matching con ParamType para determinar T.
Por ejemplo, si esta es nuestra plantilla,
template
void f(T& param); //param es una referencia
Declaramos estas variables,
int x = 27; //x es int
const int cx = x; //cx es const int
const int& rx = x; //rx es una referencia a x como const int
En diferentes llamadas, los tipos deducidos para param y T serían:
f(x); //T es int, el tipo de param es int&
f(cx); //T es const int, el tipo de param es const int&
f(rx); //T es const int, el tipo de param es const int&
Segundo Caso
- Si
expres un valor izquierdo, tantoTcomoParamTypese deducen como referencias de valor izquierdo. Esto es muy inusual: primero, es el único caso en la deducción de tipos de plantillas dondeTse deduce como referencia. Segundo, aunqueParamTypese declare con tipo de referencia de valor derecho, el resultado final es una referencia de valor izquierdo. - Si
expres un valor derecho, se usan las reglas de deducción normales (el caso uno).
Por ejemplo:
template
void f(T&& param); //param ahora es un tipo de referencia universal, && con deduccción es referencia universal.
int x = 27; //como antes
const int cx = x; //como antes
const int& rx = cx; //como antes
f(x); //x es valor izquierdo, así que T es int&,
//el tipo de param también es int&
f(cx); //cx es valor izquierdo, así que T es const int&,
//el tipo de param también es const int&
f(rx); //rx es valor izquierdo, así que T es const int&,
//el tipo de param también es const int&
f(27); //27 es valor derecho, así que T es int,
//el tipo de param es int&&
El caso de referencia universal es: si se pasa un valor izquierdo, es una referencia de valor izquierdo; si se pasa un valor derecho, es una referencia de valor derecho.
De hecho, aquí T tiene &, que puede sustituirse directamente en ParamType, eliminando duplicados (reglas de折叠/de colapso de referencias) y se descubre que es simplemente ParamType.
Tercer Caso
Esto significa que sin importar qué se pase, param se convierte en una copia: un objeto completamente nuevo. De hecho, este comportamiento de que param se convierta en un nuevo objeto afecta cómo T se deduce a partir de expr.
- Como antes, si el tipo de
expres una referencia, se ignora esta parte de referencia - Si después de ignorar la referencia de
expr,expresconst, entonces se ignora también elconst. Si esvolatile, también se ignora elvolatile
template
void f(T param); //param se maneja por valor
int x = 27; //como antes
const int cx = x; //como antes
const int& rx = cx; //como antes
f(x); //T y param son ambos int
f(cx); //T y param son ambos int
f(rx); //T y param son ambos int
Porque esto es una copia.
Casos Especiales
Si el array se pasa usando el primer caso, se preserva el tipo de array, no hay degradación. El grado de preservación nos permite escribir esto:
//En tiempo de compilación devuelve un valor constante del tamaño del array
//(el parámetro array no tiene nombre porque solo nos importa el tamaño)
template
constexpr std::size_t tamanoArray(T (&)[N]) noexcept
{
return N;
}
Aunque no tiene mucha utilidad.
Deducción de Tipos con auto
Es similar a las plantillas: la deducción de tipos con auto tiene una correspondencia directa con la deducción de tipos de plantillas. Existe una transformación muy sistemática y regular entre ellas.
template
void f(ParamType param);
f(expresion); //llamar f usando alguna expresión
//Cuando una varible se declara con auto, auto toma el papel de T en la plantilla,
//y el especificador de tipo de la varible toma el papel de ParamType.
Solo hay una excepción: las listas de inicialización.
Cómo verificar la deducción de tipos
Usa tu LSP, amigo mío.