Adrianistán

El blog de Adrián Arroyo


Novedades de C++17

- Adrián Arroyo Calle

Después de tres años de trabajo, C++17 ha sido finalmente estandarizado. Esta nueva versión de C++ incorpora y elimina elementos del lenguaje, con el fin de ponerlo al día y convertirlo en un lenguaje moderno y eficaz. El comité ISO de C++ se ha tomado muy en serio su labor, C++11 supuso este cambio de mentalidad, que se ha mantenido en C++14 y ahora en C++17, la última versión de C++.



Repasemos las noveades que incorpora C++17 respecto a C++14

if-init


Ahora podemos incluir una sentencia de inicialización antes de la condición en sentencias if y switch. Esto es particularmente útil si queremos operar con un objeto y desconocemos su validez.


// ANTES
Device dev = get_device();
if(dev.isOk()){
dev.hacerCosas();
}

// AHORA
if(Device dev = get_device(); dev.isOk()){
dev.hacerCosas();
}


Declaraciones de descomposición



Azúcar sintántico que permite mejorar la legibiliad en ciertas situaciones. Por ejemplo, en el caso de las tuplas, su uso se vuelve trivial.


// FUNCIÓN QUE DEVUELVE TUPLA
std::tuple<int, std::string> funcion();

// C++14
auto tup = funcion();
int i = std::get<0>(tup);
std::string s = std::get<1>(tup);

// C++17
auto [i,s] = funcion();



Esto funciona para multitud de estructuras de datos, como estructuras, arrays, std::array, std::map,...


std::map m = ...;
for(auto && [key, value] : m){

}


Deduction Guides



Ahora es menos necesario que nunca indicar los tipos en ciertas expresiones. Por ejemplo, al crear pares y tuplas:


// ANTES
auto p = std::pair<int,std::string>(42,"Adrianistan");

// AHORA
auto p = std::pair(42,"Adrianistan");


Esto por supuesto también sirve para estructuras y otras construcciones:


template<typename T>
struct Thingy
{
T t;
};

// Observa
Thingy(const char *) -> Thingy<std::string>;

Thingy thing{"A String"}; // thing.t es de tipo std::string


template auto




// ANTES
template <typename T, T v>
struct integral_constant
{
static constexpr T value = v;
};
integral_constant<int, 2048>::value
integral_constant<char, 'a'>::value

// AHORA
template <auto v>
struct integral_constant
{
static constexpr auto value = v;
};
integral_constant<2048>::value
integral_constant<'a'>::value


Fold expressions



Imagina que quieres hacer una función suma, que admita un número ilimitado de parámetros. En C++17 no se necesita apenas código.


template <typename... Args>
auto sum(Args&&... args) {
return (args + ... + 0);
}


Namespaces anidados



Bastante autoexplicativo


// ANTES

namespace A{
namespace B {
bool check();
}
}

// AHORA

namespace A::B {
bool check();
}


Algunos [[atributos]] nuevos



[[maybe_unused]]


Se usa para suprimir la advertencia del compilador de que no estamos usando una determinada variable.


int x = 5;
[[maybe_unused]] bool azar = true;
x = x + 10


[[fallthrough]]



Permite usar los switch en cascada sin advertencias del compilador.

switch (device.status())
{
case sleep:
device.wake();
[[fallthrough]];
case ready:
device.run();
break;
case bad:
handle_error();
break;
}


Variables inline



Ahora es posible definir variables en múltiples sitios con el mismo nombre y que compartan una misma instancia. Es recomendable definirlas en un fichero de cabecera para luego reutilizarlas en ficheros fuente.


// ANTES
// en una cabecera para que la usasen los demás
extern int x;

// solo en un fichero fuente, para inicializarla
int x = 42;
// AHORA

// en la cabecera
inline int x = 42;



if constexpr



Ahora es posible introducir condicionales en tiempo de compilación (similar a las macros #IFDEF pero mejor hecho). Estas expresiones con constexpr, lo que quiere decir que son código C++ que se evalúa en tiempo de compilación, no de ejecución.


template<class T>
void f (T x)
{
if constexpr(std:: is_integral <T>::value) {
implA(x);
}
else if constexpr(std:: floating_point <T>::value) {
implB(x);
}
else
{
implC(x);
}
}


std::optional



Tomado de la programación funcional, se incorpora el tipo optional, que representa un valor que puede existir o no. Este tipo ya existe en Rust bajo el nombre de Option y en Haskell como Maybe.


std::optional opt = f();
if(opt)
g(*opt);

// otra opción de uso si queremos proveer de un reemplazo
std::optional opt = f();
std::cout << opt.value_or(0) << std::endl;


std::variant



Descritas como las unions pero bien hechas. Pueden contener variables de los tipos que nosotros indiquemos.


std::variant<int, double, std::vector> precio; // precio puede ser un int, un double o un std::vector

// comprobar si el valor en un variant es de un determinado tipo
if(std::holds_alternative<double>(precio))
double x = std::get<double>(precio);


std::any



Si con std::variant restringimos los posibles tipos de la variable a los indicados, con std::any admitimos cualquier cosa.


std::any v = ...;
if (v.type() == typeid(int)) {
int i = any_cast<int>(v);
}


std::filesystem


Se añade a la librería estándar este namespace con el tipo path y métodos para iterar y operar con directorios. Dile adiós a las funciones POSIX o Win32 equivalentes.


#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

void main(){
fs::path dir = "/";
dir /= "sandbox";
fs::path p = dir / "foobar.txt";
std::cout << p.filename() << "\n";
fs::copy(dir, "/copy", fs::copy_options::recursive);
}


Algoritmos en paralelo



Muchos de los algoritmos de STL ahora pueden ejecutarse en paralelo bajo demanda. Con std::execution::par indicamos que queremos que el algoritmo se ejecute en paralelo.


std::sort(std::execution::par, first, last);


¿Qué novedades se esperan en C++20?


Ya hemos visto lo que trae C++17. Ahora veremos que se espera que traiga C++20 en 2020.

  • Módulos. Reemplazar el sistema de includes

  • Corrutinas. Mejorar la programación asíncrona

  • Contratos. Mejorar la calidad del código

  • Conceptos. Mejorar la programación genérica

  • Redes. Estandarizar la parte de red en C++ tal y como se ha hecho con std::filesystem

  • Rangos. Nuevos contenedores



Referencias:





 

Comentarios

eferion
El artículo está interesante pero has pasado por alto algunos detalles: - if-init: También funciona con switch - Declaraciones de descomposición: si usas C++14 (e incluso C++11) también podrías haber usado std::tie para explotar la tupla (que si no al final parece que C++14 es casi antediluviano): int i; std::string s; std::tie(i,s) = funcion(); - Deduction Guides: podías haber explicado que existe std::make_pair y que esta nueva característica pretende acabar con esas funciones de ayuda. - Algunos [[atributos]] nuevos: Estaría bien añadir [[nodiscard]] que es bastante importante también - if constexpr: Se te olvidó mencionar que, a diferencia de #ifdef, con if constexpr tanto la opción if como la else tienen que ser válidas en tiempo de compilación. Y luego hay algunas características bastante interesantes que no has ni mencionado: - Los atributos se pueden añadir a espacios de nombres y enumerados namespace [[deprecated]] versionVieja{ void func(); } - Las funciones constexpr se pueden usar para resolver templates: constexpr bool func(int n) { return n%2==0; } template void func2() { std::cout &lt;&lt; par &lt;&lt; &#039;\n&#039;; } func2(); // Imprime 1 func2(); // imprime 0 - constexpr se puede usar con lambdas: constexpr auto lambda = []( int n ) { return n%2==0; }; Hay más cosillas pero no entran en un comentario :)
aarroyoc
Muchas gracias por tu comentario. Siempre es un placer leer comenarios así. Respecto a los detalles que comentas al principio, estaba al tanto de ellos. De hecho lo de switch si te fijas sí que lo puse, solo que no hice ejemplo, quizá no se ha entendido bien. Sobre desestructurar la tupla con std::tie y el uso de std::make_pair aparece en los artículos que enlazo al final pero al ser varias maneras de hacer lo mismo en C++14 preferí no ponerlo aquí. El atributo de [[nodiscard]] lo he ignorado a posta porque puede ser subóptimo y su uso solo es justificable en casos muy concretos. Sobre el resto de cosas ahí me has pillado :) No sabía que se hubiese añadido tanto a constexpr y que ahora los namespaces tuvieran atributos. Saludos
eferion
Gracias por los comentarios y por el blog. No suelen abundar los blogs en español que traten los temas en serio... creo que me pasaré por aquí de vez en cuando. Lo de [[nodiscard]] tiene su uso sobretodo en código legado, donde es muy sencillo encontrar punteros retornados sin control alguno. Así con un esfuerzo mínimo pones al compilador a chequear una fuente de fugas de memoria. En cuanto al resto, C++14 en comparación con C++17 es un "minor upgrade". Esperemos que C++20 siga la misma progresión y el lenguaje consiga ponerse al día.
[&#8230;] al fin y al cabo también necesitas algo de suerte. En mi caso este año publiqué el post de Novedades en C++17 que fue portada en meneame. Eso genera muchísimas visitas de golpe. Varios conocidos se pusieron [&#8230;]

Añadir comentario

Todos los comentarios están sujetos a moderación