C++17
C++17 es la versión más reciente 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 reemplaza 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
Dado que la especificación para C++17 apenas comienza, la siguiente lista es solo un comienzo, se añadirán muchas características en los próximos años.[4]
Variables en línea
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]
auto
desde la lista de inicialización arriostrada [5][6]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
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
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
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]
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]
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
boost::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:
#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]
if
switch
[11]
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
std::any
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
std::optional
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
std::variant
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
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
- La adición de un mensaje de texto por defecto para
static_assert
. Ya no es obligatorio especificar el error por defecto que muestra elstatic_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 clasebasic_string_view
describe un objeto que puede referirse a una sentencia contigua constante de objetoschar-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 objetoCharT
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]
- 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]
- Un tiempo de compilación estático
if
(condicional) con el formularioif constexpr(expression)
.
- Algunas extensiones en asignación alineada de memoria.
Referencias
- Herb Sutter (6 de septiembre de 2017). «C++17 is formally approved» (en inglés). Consultado el 7 de septiembre de 2017.
- «ISO/IEC 14882:2017». International Organization for Standardization (en inglés). Consultado el 6 de diciembre de 2017.
- 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.
- «Estado de la implementación C++1z».
- «N3922: Nuevas reglas para la deducción auto desde la lista de inicialización arriostrada (James Dennett)».
- «Actualizaciones a mi reporte de viaje».
- «N4295: Expresiones plegables (Andrew Sutton, Richard Smith)».
- «Nuevos documentos de lenguaje núcleo adoptadas para C++17».
- «N4230: Definición de espacio de nombres anidados (Robert Kawulak, Andrew Tomazos)».
- «Actualizaciones a mi reporte de viaje».
- «Inicialziadores if y switch (Thomas Köppe)».
- «N3928: Extensión static_assert, v2 (Walter E. Brown)».
- «std::basic_string_view - cppreference.com». en.cppreference.com. Consultado el 23 de junio de 2016.
- «N3981: ¿Eliminando trígrafos? (Richard Smith)». 6 de mayo de 2014.
- 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
- «N4051: Permite typename en un parámetro de una plantilla (Richard Smith)».
- «N4259: Texto para std::uncaught_exceptions (Herb Sutter)».
- «N4266: Atributos para espacios de nombres y enumeradores (Richard Smith)».
- «N4267: Adición de caracteres literales u8 (Richard Smith)».
- «N4268: Constante evaluación para todos los argumentos de las plantillas sin tipo (Richard Smith)».
- «N4279: Mejora de la interfaz de inserción para los mapas de clave única (Thomas Köppe)».
- «Nuevos documentos de la biblioteca estándar adoptados para C++17».
- «N4280: Non-member size() and more (Riccardo Marcangelo)».
- «N4284: Iteradores contiguos (Jens Maurer)».
- «N4190: Removiendo auto_ptr, random_shuffle(), y antiguos <functional> Stuff (Stephan T. Lavavej)».
- «N4361: Proyecto de trabajo, entensiones C++ para conceptos (Andrew Sutton)».
- «Stroustrup: Reflexiones sobre C++17 - Una entrevista (Sergio De Simone)».
- «Sintaxis de llamada unificada: x.f(y) and f(x,y) (Bjarne Stroustrup, Herb Sutter)».
- «El Paralelismo TS debe ser estandarizado (inglés)».
- «Funciones especiales matemáticas para C++17, v5 (en inglés)».
- «Adoptar los componentes de las librerías de fundamentos V1 TS para C++17 R1 (en inglés)».
- «Estado actual».
- «N4089: Conversiones seguras en unique_ptr<T[]> (Geoffrey Romer)».
- «LWG 2228: Pequeño cambio a unique_ptr (Howard Hinnant)».
- «P0033R1: Reactivación de shared_from_this (Jonathan Wakely y Peter Dimov)».
Enlaces externos
- Norma ISO/IEC 14882:2017 en la página web de la Organización Internacional de Normalización
- Working Draft, Standard for Programming Language C++ N4700. Último borrador publicado del estándar (16 de octubre de 2017) (en inglés)