Deducción de tipos en C++0x con decltype
El otro día veíamos la deducción automática de tipos usando auto, con la que el tipo de una variable se deduce de la expresión con la que se inicializa. Para todos los demás casos, desde hace ya varios años, muchos compiladores proporcionan alguna forma de deducción de tipos, normalmente algo parecido a __typeof. El comité de C++ consideró seriamente incluirlo en estándar, pero finalmente lo descartaron en favor de una idea nueva: decltype.
La diferencia entre __typeof y decltype es que el primero obtiene simplemente el tipo de una expresión, pero el segundo depende de aquello sobre lo que opera, aplicando la primera de estas reglas que se cumpla:
- Si la expresión es una variable, el resultado es el tipo con el que se declaró la variable.
- Si la expresión es una llamada a una función, el resultado es el tipo de retorno de la función.
- Si es un L-valor, el resultado es una referencia al tipo de la expresión.
- En otro caso, el resultado es el tipo de la expresión.
Pero… ¿cuál es la diferencia? Veamos unos ejemplos:
int i = 0; int &r = i; int &&rr = i; decltype(i) a = i; //int a (regla 1) decltype(3) b = i; //int a (regla 4) decltype(r) c = i; //int &c (regla 1) decltype(rr) d = 5; //int &&d (regla 1)
(Si usáramos __typeof, tal y como lo define gcc, todas esas variables serían de tipo int).
En el caso de que la expresión sea una variable, se pueden añadir paréntesis para que pase a aplicarse la regla 3 (sin embargo, este truco no vale para evitar la regla 2):
int i = 0; decltype(i) a = i; //int a (regla 1) decltype((i)) b = i; //int &a (regla 3)
Nótese que las reglas de decltype y las de auto son diferentes, por lo que las siguientes declaraciones no son necesariamente equivalentes:
auto v = expr; decltype(expr) v = expr;
La idea es que el programador tenga ambas opciones y pueda escoger la que más apropiada le resulte. Dicho esto, lo cierto es que el principal uso de decltype es para determinar el tipo de retorno de una función, es decir la regla 2. Esto es necesario para completar el forwarding de funciones:
decltype(bar()) foo() { return bar(); }
Aquí, la función foo() devuelve el mismo tipo (incluidas referencias) que bar().
Este, claro está, es un ejemplo bastante tonto, pero es que si le añadimos argumentos a las funciones tenemos un problema:
decltype(bar(x)) foo(int x) { return bar(x); }
¡Esto no compila! El nombre del argumento x se introduce en la declaración del argumento, por lo que en el decltype todavía no existe. Cierto que podríamos cambiar la x por cualquier otra expresión del mismo tipo (una constante, por ejemplo), pero entonces se pierde si es L-valor o R-valor). Y si añadimos templates la cosa se complica más y habría que hacer rodeos extraños:
template <typename A, typename B> decltype(bar(*(A*)0, *(B*)0)) foo(A a, B b) { return bar(a, b); }
Aquí, *(A*)0 es un apaño para escribir una expresión de tipo A que no asuma nada de ese tipo, y que además sea un L-valor (se cuenta con que decltype no evalúa la expresión, claro). Esto es absurdamente complicado, y además no es algo que me gustase enseñar por ahí.
Para evitar esto, C++0x define una nueva sintaxis para declarar una función:
//C++ clásico int funcion(); //nueva C++0x auto funcion() -> int;
(¡auto otra vez!)
Que esta forma de declarar funciones sea nueva no significa que sea preferible. La forma normal sigue siendo la tradicional. Esta se debe utilizar solamente cuando los nombres de los argumentos son necesarios para definir el tipo de retorno. Ahora el ejemplo de antes resulta bien sencillo:
template <typename A, typename B> auto foo(A a, B b) -> decltype(bar(a,b)) { return bar(a, b); }
La expresión bar(a, b) se escribe dos veces, pero eso es un inconveniente menor.
Y ya que estamos, podemos hacer el forwarding perfecto:
template <typename A, typename B> auto foo(A &&a, B &&b) -> decltype(bar(std::forward<A>(a), std::forward<B>(b))) { return bar(std::forward<A>(a), std::forward<B>(b)); }
Conclusión: decltype no es algo que vayamos a utilizar todos los días, y lo mismo con los prototipos invertidos, pero conviene conocerlos para que no nos pillen por sorpresa cuando los veamos por ahí.
Artículos relacionados:
- Tipos automáticos en C++0x con auto La palabra reservada auto es casi con toda seguridad la...
- 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...
- Funciones lambda en C++0x Una de las funcionalidades menos esperadas, pero en mi opinión...
- Templates con número variable de argumentos en C++0x Allá por el año 1999 se publicaba el nuevo y...
RSS
Deja un comentario