C++17

De Wikipedia, la enciclopedia libre

C++17 fue una versión del estándar del lenguaje de programación C++. La especificación final de C++17 fue aprobada por el comité de estandarización de C++ (WG21) de la Organización Internacional de Normalización (ISO) el 6 de septiembre de 2017[1]​ y fue publicada oficialmente como la norma ISO/IEC 14882:2017 en diciembre del mismo año.[2]​ C++17 reemplazó a la anterior especificación C++14 publicada en el año 2014. El nombre sigue la tradición de denominar a las versiones del lenguaje C++ a partir de la fecha de publicación de la especificación. Antes de conocerse la fecha exacta de su publicación, el borrador del estándar era informalmente conocido como C++1z para distinguirlo de C++11 (C++1x) y C++14 (C++1y). La próxima revisión de ésta especificación se denominará C++20.[3][1]

Nuevas características[editar]

Las nuevas características que presentó en aquel entonces fueron estas.[4]

Variables en línea[editar]

Permite la definición de variables en archivos de cabecera. El compilador elige donde se instancia la variable.

El especificador inline puede ser aplicado tanto a funciones como variables. Una variable declarada inline tiene la misma semántica que una función declarada inline: puede ser definida, de forma idéntica, en múltiples unidades de traducción (esto es, un archivo .c/.cpp una vez que todos los archivos de cabecera han sido incluidos), y ha de ser definida en cada una de esas unidades de traducción donde va a ser usada.

El comportamiento del programa es como si hubiese exactamente una variable.

En C++14, aunque había soporte para usar variables estáticas en plantillas de clase, no había modo conveniente de hacerlo. Por tanto, uno debía recurrir a trucos como  este:

template< class Dummy > 
struct Kath_ 
{
 static std::string const hi; 
};

template< class Dummy > 
std::string const Kath_<Dummy>::hi = "Zzzzz..."; 
using Kath = Kath_<void>; //Permite escribir `Kath::hi`.

Y desde C++17, podemos hacer en un archivo de encabezado:

struct Kath 
{ 
static std::string const hi; 
}; 

inline std::string const Kath::hi = "Zzzzz..."; //Más simple

Nuevas reglas para la deducción auto desde la lista de inicialización arriostrada[5][6][editar]

Para la inicialización de copia de lista, la deducción auto deducirá un std::initializer_list (si los tipos de entradas en la lista de inicialización arriostrada son todos idénticos) o de lo contrario estará mal constituida.

Para la inicialización de listas directa:

  • Para una lista de inicialización arriostrada con un solo elemento, la deducción automática deducirá a partir de esa entrada.
  • Para una lista de inicialización arriostrada con más de un elemento, la deducción automática se constituirá de forma errónea.

Las reglas para los for basados en rango no serán afectadas por los cambios propuestos aquí, ya que las reglas para la inicialización de copia de lista no se han modificado.

Enlaces Estructurados[editar]

Enlaza los nombres especificados a subobjetos o elementos del inicializador.

Como una referencia, un enlace estructurado es un alias a un objeto existente. Sin embargo, y al contrario que en una referencia, el tipo del enlace estructurado no tiene que ser un tipo referencia.

La declaración de un enlace estructurado introduce todos los identificadores en la lista de identificadores como nombres del scope circundante y los enlaza a subobjetos o elementos del objeto denotado por expresión. Los enlaces de este tipo son llamados enlaces estructurados. Lo que permite expresiones como:

const auto [it, inserted] = map.insert( {"foo", bar} );
//Crea las variables it e inserted con el tipo deducido del par que map::insert retorna.

auto [a, b] = getTwoReturnValues();

std::invoke[editar]

Invoca cualquier llamable (puntero de función, función, puntero miembro) con una única sintaxis. Viene del concepto del estándar INVOKE.

Como ejemplo, considera el deref_fn wrapper, que debería aceptar cualquier objeto llamable f y retornar un functor que invoca al llamable proveído y desreferencia el resultado.

Usando C++14, podría implementarse así:

template<typename F, std::enable_if_t<!std::is_member_pointer<std::decay_t<F>>{}, int> = 0>
auto deref_fn(F&& f) 
{ 
return [f](auto&&... args) { return *f(std::forward<decltype(args)>(args)...); };
} 

template<typename F, std::enable_if_t<std::is_member_pointer<std::decay_t<F>>{}, int> = 0>
auto deref_fn(F&& f)
{ 
return [mf = std::mem_fn(f)](auto&&... args) { return *mf(std::forward<decltype(args)>(args)...); };
}

La función invoke propuesta permite una implementación más simple, que no depende del uso de la sobrecarga de función SFINAE:

template<typename F>
auto deref_fn(F&& f) 
{ 
return [f](auto&&... args) { return *std::invoke(f, std::forward<decltype(args)>(args)...); };
}

Generalización del sistema basado en rango para bucles[editar]

El actual sistema basado en rango para bucles es demasiado restrictivo. El iterador end nunca es incrementado, decrementado o desreferenciado. El requerimiento de que sea un iterador no tiene sentido por tanto.

Hoy en día, los algoritmos STL requieren que el begin y end de un rango tengan el mismo tipo. Es algo sensato teniendo en cuenta que los algoritmos toman el begin y el end de un rango como parámetros separados. En la ausencia de comprobación, permitir que sus tipos difieran hace bastante fácil el pasar iteradores que no se correspondan entre ellos. Esto no ocurre con el sistema basado en rango para bucles, puesto que se hace cargo de los rangos de forma completa.

Perder los requerimientos de tipo basados en rango para bucles proporciona a los usuarios de Ranges TS la mejor experiencia posible. Si los usuarios crean rangos con Ranges TS que no son usables con los rangos basados en bucle por defectos, se sentirían frustrados, y no tendrá mucha utilidad. Con toda probabilidad, una solución basada en macro como BOOST_FOREACH será inventada para llenar el hueco.

Por tanto, es un apoyo a centinelas, o iteradores end que no son del mismo tipo que el begin iterator, que ayuda con los bucles terminados en null y parecidos.

Expresiones plegables[7][8][editar]

Si necesitas una función suma, que admita un número ilimitado de parámetros, en C++17 se puede hacer de una forma muy simple:

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

Definición de espacio de nombres anidados[9][10][editar]

Permite declarar varios namespaces en una sola  sentencia:

//En C++14
namespace A{
    namespace B {
        bool check();
    }
}

//En C++17 
namespace A::B {
    bool check();
}

Una biblioteca de sistema de archivos basado en boost::filesystem[editar]

Se añade a la librería estándar este namespace con el tipo path y métodos para iterar y operar con directorios:

#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);
}

Inicializadores if switch [11][editar]

Inicializadores en las sentencias if y switch, ahora permiten evaluar condiciones en inicializadores que no son convertibles a booleanos.

//Antes
auto p = map.try_emplace(key, value);   
if (!p.second) {     
    //ERROR
} else {     
    //OK
}

//Ahora
if (auto p = map.try_emplace(key, value); !p.second) {
    //ERROR
} else {
    //OK
}

Clase std::any[editar]

Un objeto de la clase any almacena una instancia de cualquier tipo que satisface los requisitos del constructor o que está vacío,. El objeto almacenado es conocido como objeto contenido. Dos estados son equivalentes si ambos están vacíos o si los objetos contenidos son iguales. Las implementaciones de esta clase deben evitar el uso de memoria reservada dinámicamente para el objeto contenido.

std::any v = ...;
if (v.has_value()) {
   // se cumple si no es un entero
   int i = any_cast<int>(v);
}

Plantilla de clase std::optional[editar]

Representa objetos que pueden o no existir. El uso más común para esto es asignarlo a un valor que devuelve una función que puede fallar. El objeto contenido puede ser inicializado después de que el objeto opcional haya sido inicializado, y puede ser destruido antes de que el objeto opcional lo haya sido. El estado de inicialización del objeto contenido está monitorizado por el objeto opcional.

std::optional opt = f();
std::cout << opt.value_or(0) << std::endl;

Plantilla de clase std::variant[editar]

La plantilla de clase std::variant representa un tipo de unión segura. Una instancia de  std::variant en cualquier tiempo dado sostiene un valor de los tipos alternativos o, en caso de error, ningún valor. A la variante no se le permite asignar memoria dinámicamente.

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

Fallthrough[editar]

Permite usar los switches en cascada sin que el compilador emita warning.

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

Otras características[editar]

  • La adición de un mensaje de texto por defecto para static_assert. Ya no es obligatorio especificar el error por defecto que muestra el static_assert. [12]
  • La adición de std::string_view, una referencia no propietaria a una secuencia de caracteres o sectores, que es de solo lectura. La plantilla de clase basic_string_view describe un objeto que puede referirse a una sentencia contigua constante de objetos char-like con el primer elemento de la secuencia en la posición 0. Una implementación típica consiste en dos elementos: un puntero a un objeto CharT y un tamaño.[13]
  • Eliminación de trígrafos. Pueden seguir siendo usados si son implementados en el código fuente.[14][15]
  • Permite typename en una parámetro de una plantilla.[16]
  • std::uncaught_exceptions, como reemplazo de std::uncaught_exception.[17][8]
  • Atributos para espacios de nombres y enumeradores.[18][8]
  • Constante evaluación para todos los argumentos de las plantillas sin tipo.[20][8]
  • Nuevas funciones de inserción para std::map y std::unordered_map.[21][22]
  • Acceso al contenedor Uniforme.[23][22]
  • Definición de "Iteradores contiguos".[24][22]
  • La eliminación de algunos tipos y funciones en desuso como std::auto_ptr, std::random_shuffle o adaptadores de función antiguas.[25][6]
  • Conceptos, proporcionando "especificación y verificación de las restricciones sobre los argumentos de plantilla".[26][27]
  • Sintaxis de llamada unificada.[28][27]
  • std::apply, toma una función o algo que se comporte como tal  y una tupla, y desempaqueta la tupla en la llamada.
  • Versiones paralelas de algoritmos STL.[29]​ Con std::execution::par indicamos que queremos que el algoritmo se ejecute en paralelo: std::sort(std::execution::par, first, last);. Los nuevos algoritmos compatibles son: for_each_n, reduce, transform_reduce, exclusive_scan, inclusive_scan, transform_exclusive_scan, transform_inclusive_scan.
  • Funciones especiales de matemática adicional.[30]
  • La mayoría de las librerías de fundamentos TS I (ejemplos: std::optional, std::any).[31][32]
  • Un tiempo de compilación estático if (condicional) con el formulario if constexpr(expression).
  • Algunas extensiones en asignación alineada de memoria.
  • Plantilla de deducción de constructores, permitiendo pair(5.0, false) en vez de pair<double,bool>(5.0, false).
  • Pequeños cambios a punteros.[33][34][35]

Véase también[editar]

Referencias[editar]

  1. a b Herb Sutter (6 de septiembre de 2017). «C++17 is formally approved» (en inglés). Consultado el 7 de septiembre de 2017. 
  2. «ISO/IEC 14882:2017». International Organization for Standardization (en inglés). Consultado el 6 de diciembre de 2017. 
  3. Herb Sutter (30 de junio de 2016). «Trip report: Summer ISO C++ standards meeting (Oulu)» (en inglés). Consultado el 7 de septiembre de 2017. 
  4. «Estado de la implementación C++1z». 
  5. «N3922: Nuevas reglas para la deducción auto desde la lista de inicialización arriostrada (James Dennett)». 
  6. a b «Actualizaciones a mi reporte de viaje». 
  7. «N4295: Expresiones plegables (Andrew Sutton, Richard Smith)». 
  8. a b c d e «Nuevos documentos de lenguaje núcleo adoptadas para C++17». 
  9. «N4230: Definición de espacio de nombres anidados (Robert Kawulak, Andrew Tomazos)». 
  10. «Actualizaciones a mi reporte de viaje». 
  11. «Inicialziadores if y switch (Thomas Köppe)». 
  12. «N3928: Extensión static_assert, v2 (Walter E. Brown)». 
  13. «std::basic_string_view - cppreference.com». en.cppreference.com. Consultado el 23 de junio de 2016. 
  14. «N3981: ¿Eliminando trígrafos? (Richard Smith)». 6 de mayo de 2014. 
  15. Comentario IBM sobre el preparado para un trígrafo adverso futuro en C++17, documento IBM N4210, 10 de octubre de 2014. Autores: Michael Wong, Hubert Tong, Rajan Bhakta, Derek Inglis
  16. «N4051: Permite typename en un parámetro de una plantilla (Richard Smith)». 
  17. «N4259: Texto para std::uncaught_exceptions (Herb Sutter)». 
  18. «N4266: Atributos para espacios de nombres y enumeradores (Richard Smith)». 
  19. «N4267: Adición de caracteres literales u8 (Richard Smith)». 
  20. «N4268: Constante evaluación para todos los argumentos de las plantillas sin tipo (Richard Smith)». 
  21. «N4279: Mejora de la interfaz de inserción para los mapas de clave única (Thomas Köppe)». 
  22. a b c «Nuevos documentos de la biblioteca estándar adoptados para C++17». 
  23. «N4280: Non-member size() and more (Riccardo Marcangelo)». 
  24. «N4284: Iteradores contiguos (Jens Maurer)». 
  25. «N4190: Removiendo auto_ptr, random_shuffle(), y antiguos <functional> Stuff (Stephan T. Lavavej)». 
  26. «N4361: Proyecto de trabajo, entensiones C++ para conceptos (Andrew Sutton)». 
  27. a b «Stroustrup: Reflexiones sobre C++17 - Una entrevista (Sergio De Simone)». 
  28. «Sintaxis de llamada unificada: x.f(y) and f(x,y) (Bjarne Stroustrup, Herb Sutter)». 
  29. «El Paralelismo TS debe ser estandarizado (inglés)». 
  30. «Funciones especiales matemáticas para C++17, v5 (en inglés)». 
  31. «Adoptar los componentes de las librerías de fundamentos V1 TS para C++17 R1 (en inglés)». 
  32. «Estado actual». 
  33. «N4089: Conversiones seguras en unique_ptr<T[]> (Geoffrey Romer)». 
  34. «LWG 2228: Pequeño cambio a unique_ptr (Howard Hinnant)». 
  35. «P0033R1: Reactivación de shared_from_this (Jonathan Wakely y Peter Dimov)». 

Enlaces externos[editar]