Tipos automáticos en C++0x con auto
La palabra reservada auto es casi con toda seguridad la más inútil de todo el lenguaje C (seguida de lejos por register. Tanto es así que mucha gente ni siquiera sabe que existe porque no la ha visto nunca. Una variable local se declara auto para hacerla automática, es decir que se crea en el punto de declaración y se destruye al salir del bloque declarado. Pero una variable local ya es automática por defecto, a no ser que se defina static o extern, por lo que auto no se usa nunca.
La nueva versión de C++, C++0x, le da un significado nuevo y totalmente diferente a esta palabra reservada, implementando una de las características más deseadas por los ususarios del lenguaje: la deducción automática de tipos.
El problema
Muchos lenguajes modernos sufren del siguiente efecto al crear un objeto dinámico:
Tipo *x = new Tipo(args);
El problema es que el nombre del Tipo hay que escribirlo dos veces, de forma redundante
Otra situación es que en ocasiones el tipo de una variable puede no fácil de escribir:
std::map< std::string, std::list<Objeto*> > dic; //... for (std::map< std::string, std::list<Objeto*> >::iterator it = dic.begin(); it != dic.end(); ++it) { const std::string &clave = it->first; std::list<Objeto*> &valor = it->second; for (std::list<Objeto*>::iterator obIt = valor.begin(); obIt != valor.end(); ++it) { Objeto *ob = *obIt; //... } }
Los iteradores son especialmente fastidiosos, porque los usaría mucho más si no fueran tan largos de escribir. Esto puede aliviarse un poco utilizando typedef, pero sigue siendo molesto.
A veces, sobre todo cuando hay templates implicados, el tipo de una expresión puede ser difícil, o incluso imposible, de determinar (para el programador, claro, no para el compilador):
#include <functional> using std::placeholders::_1; int mult(int a, int b) { return a*b; } int main() { ??? doble = std::bind(mult, 2, _1); //std::bind() devuelve "unspecified"! std::cout << doble(3) << std::endl; //escribe "6" }
Finalmente, los programadores perezosos a menudo usan (¿usamos?) int para todos los tipos integrales, sin pensar demasiado, sin darnos (digo, darse) cuenta de cuál es el apropiado, y esto puede dar problemas, tanto de signo como de desbordamientos:
off_t x = lseek(fd, 0, SEEK_END); size_t sz = sizeof(variable); ssize_t res = read(fd, buf, sz);
La solución
El caso es que el compilador siempre sabe qué tipo tienen todas y cada una de las expresiones del programa (excepto en los templates, que no lo sabe hasta que se instancian). Entonces no hay razones para tener que indicar el tipo manualmente, basta con indicar auto. Los ejemplos anteriores se quedan así:
auto *x = new Tipo(args);
std::map< std::string, std::list<Objeto*> > dic; //... for (auto it = dic.begin(); it != dic.end(); ++it) { const auto &clave = it->first; auto &valor = it->second; for (auto obIt = valor.begin(); obIt != valor.end(); ++it) { auto ob = *obIt; //... } }
#include <functional> using std::placeholders::_1; int mult(int a, int b) { return a*b; } int main() { auto doble = std::bind(mult, 2, _1); std::cout << doble(3) << std::endl; //escribe "6" }
auto x = lseek(fd, 0, SEEK_END); auto sz = sizeof(variable); auto res = read(fd, buf, sz);
Las reglas
El estándar de C++0x especifica el funcionamiento de auto con elegancia, ¡en una única página! Básicamente, y simplificando un poco, dice que auto solo puede usarse en variables locales y globales con inicialización (además de en expresiones new y en el retorno de funciones, pero eso funciona de otra manera y lo dejaré para otro día). Luego define el algoritmo de deducción del tipo diciendo que es el mismo que para una función template en el que la declaración se convierte en argumento. Por ejemplo:
1 2 3 | auto *x = expr; template<typename A> void fun(A *x); fun(expr); |
El tipo deducido para auto en la línea 1 es el mismo que para A en la línea 3.
Y finalmente se zanja de una vez por todas el debate de cómo debería comportarse una declaración auto múltiple:
1 | auto a=1, b=1.0; //a,b son del mismo tipo? |
¿Se deduce cada variable independientemente? ¿O se deducen de forma global? Pueden buscarse argumentos a favor y en contra de ambos. La solución final es que se deducen por separado, pero si no resulta exactamente el mismo tipo en todas ellas se genera un error.
Ahora solo queda recordar las reglas de deducción de tipos para una función template. A continuación presentaré las reglas más importantes, resumidas, pero en forma de declaraciones auto, que son más fáciles de escribir:
- auto x = expr; El tipo deducido es el de la expresión, descartando const si lo tiene. Excepto si la expresión es un array o función, en cuyo caso se convierte primero a puntero.
- const auto x = expr; El tipo deducido es el mismo que si no hubiera const, y x es una constante de ese tipo.
- auto &x = expr; El tipo deducido es el de la expresión, y x es una referencia a ese tipo. Si expr es un R-valor (objeto temporal o literal) generará un error de compilación.
- const auto &x = expr; Igual que la anterior, pero x es una referencia constante, por lo tanto expr sí puede ser un R-valor.
- auto &&x = expr; Esta es interesante porque hace el equivalente al forwarding, pero con una variable:
- Si expr es un L-valor entonces el tipo deducido es una referencia a L-valor al tipo de expr, y por lo tanto x es una referencia a L-valor vinculada al resultado de la expresión. (Sí, he escrito L-valores, no es una errata).
- Si expr es un R-valor entonces el tipo deducido es el de expr, y x es una referencia a R-valor vinculada a la expresión, (creando un temporal si es necesario).
No sé si me he explicado bien. Para aclararme veamos unos ejemplos y sus equivalencias, a ver si no me equivoco en ninguno:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | auto a = 42; // int a = 42; const auto b = a; // const int b = a; auto &c = a; //int &c = a; auto &d = b; //const int &d = a; auto *e1 = &a; //int *e1 = &a; auto e2 = &a; //int *e2 = &a; auto &g = 0; //int &g = 0; -> error! auto &&h = a; //int &h = a; auto &&i = 3; //int &&i = 3; auto &&j = b; //const int &&j = b; auto *f1 = "hola"; //const char *f1 = "hola"; auto f2 = "hola"; //const char *f2 = "hola"; const auto *f3 = "hola"; //const char *f3 = "hola"; auto *&&p1 = &a; //int *&&p1 = &a; auto &&p1 = &a; //int *&&p1 = &a; |
Artículos relacionados:
- Deducción de tipos en C++0x con decltype El otro día veíamos la deducción automática de tipos usando...
- Referencias a R-valor (2ª parte) En el post anterior describía cómo las referencias a R-valor...
- Referencias a R-valor (1ª parte) En un post anterior comentaba que en C++ no se...
- Templates con número variable de argumentos en C++0x Allá por el año 1999 se publicaba el nuevo y...
- L-valores y R-valores error: se requiere un l-valor como operando izquierdo de la...
RSS
Deja un comentario