Artículos de la serie:
- Categorías de valor. Referencias lvalue y rvalue
- Inferencia automática de tipos (auto)
- Always auto: Una sintaxis moderna para C++
- Referencias de reenvío y auto&&
Introducción

Así sucede, en efecto, al definir un concepto (C++20):
template<typename I, typename S, typename Comp>
concept Insertion_sortable = std::bidirectional_iterator<I>
and std::movable<std::iter_value_t<I>>
and std::sentinel_for<S, I>
and std::strict_weak_order<Comp, std::iter_value_t<I>,
std::iter_value_t<I>>;
un alias para un tipo:
using Phone_book = std::map<std::string, std::vector<int>>;
o al indicar el tipo de retorno de una función mediante el especificador auto:
auto read_data(std::string_view file_name) -> Phone_book { /* ... */ }
Notemos, en todos los casos anteriores, cómo el flujo de lectura se produce de forma natural desde el margen izquierdo --donde figura el nombre del concepto Floating_point, del alias Phone_book o de la función read_data-- hacia el derecho --donde encontramos la definición del concepto, el tipo original para el que definimos el alias y el tipo retornado por la función, respectivamente.
Estilo 'always auto'
Herb Sutter (convener del comité ISO WG21 de estandarización del lenguaje) identificó en su post GotW#94 la emergencia de un estilo declarativo moderno y de aplicación general para C++ de la forma
categoría nombre = tipo y/o inicializador
donde la categoría es sustituida por auto, using, etcétera, dependiendo del contexto. Para más información, puedes consultar la referencia [1].
- A la izquierda del signo igual, situaremos el nombre de la variable, precedido de la palabra clave auto y, si fuese necesario, de una lista de calificadores/especificadores adicionales (const, constexpr, static, thread_local, etcétera).
- A la derecha del signo igual, indicaremos el tipo de la variable (siempre que no deseemos su inferencia automática a través de auto) y, entre llaves {}, sus valores de inicialización.
Así, podemos escribir:
auto option = char{'y'};
auto counter = int{0};
auto width = double{3.4};
auto height = float{1.2f};
auto name = std::string{"Edmond Dantes"};
auto const numbers = std::array<int, 5>{1, 2, 3, 4, 5};
Los casos anteriores pueden simplificarse aún más, sin dejar lugar para la ambigüedad, si nos servimos de la inferencia automática de tipos, los sufijos estandarizados y la técnica CTAD (class template argument deduction, C++17):
auto option = 'y'; // char
auto counter = 0; // int
auto width = 3.4; // double
auto height = 1.2f; // float
using namespace std::literals;
auto name = "Edmond Dantes"s; // std::string (char const* si suprimimos el sufijo s)
auto const numbers = std::array{1, 2, 3, 4, 5}; // CTAD: std::array<int, 5> const
En particular, el empleo de sufijos o alias resulta necesario al trabajar con tipos de nombre compuesto:
auto m = unsigned long long{1'671'987}; // inválido
auto n = 1'671'987ull; // OK: n es unsigned long long
auto p = int*{&n}; // sintaxis inválida para punteros
template<typename T> using ptr = T*;
auto q = ptr<int>{&n}; // OK: q es de tipo int*
Con carácter general, deberíamos confiar en la inferencia automática siempre que el tipo de la variable sea evidente por el contexto. Por ejemplo:
auto i = 0; auto p = &i; // p es de tipo int*
auto q = new int{7}; // q es de tipo int*
auto r = std::make_unique<Dinosaur>("TRex"); // r es de tipo std::unique_ptr<Dinosaur>
auto v = std::vector<int>{}; auto sz = v.size(); // sz de tipo std::vector<int>::size_type
Ventajas de la nueva sintaxis
Al adoptar el estilo 'always auto' no nos movemos únicamente por criterios de consistencia entre distintos tipos de declaraciones. Es importante resaltar que esta notación evita la introducción de variables locales sin inicializar, pues expresiones como las siguientes están prohibidas:
auto i; // error de compilación
auto j = int; // error de compilación
Esta propiedad resulta crítica a la hora de producir software correcto, pues una operación de lectura sobre una variable no inicializada conduce a comportamiento indefinido (undefined behavior).
Asimismo, podemos ahora evitar un problema de parseo recurrente conocido como the most vexing parse:
S s(); // ¡declaración de función!
La expresión anterior no define una variable de tipo S construida por defecto, sino que el compilador la interpreta como la declaración de una función sin parámetros de nombre s que retorna un objeto de tipo S. La nueva sintaxis nos protege de incidencias de este tipo:
auto s = S{}; // OK: objeto inicializado por defecto
Es importante resaltar que, a partir de C++17, el compilador elude tanto la creación de un temporal como la realización de una operación posterior de movimiento al inicializar variables a través de prvalues. Ello permite emplear esta técnica incluso con objetos de clases cuyos constructores de movimiento estén declarados como delete, o en casos donde el movimiento del objeto hubiese resultado costoso (por ejemplo un array):
auto mtx = std::mutex{}; // OK, aunque std::mutex no sea movible
void thread_safe_op(std::vector<int>& v)
{
auto lck = std::lock_guard{mtx}; // OK, aunque std::lock_guard no sea movible
// ...
}
Finalmente, conviene señalar que el nuevo estilo puede facilitar el aprendizaje a aquellos programadores principiantes en C++ que, sin embargo, cuenten con experiencia previa con lenguajes más recientes como Go, Rust o Swift.
Referencias bibliográficas:
[1] https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
[2] https://www.fluentcpp.com/2018/09/28/auto-stick-changing-style/
No hay comentarios:
Publicar un comentario