9

Traducción basada en la pregunta de Yakk del SO en Inglés.


Ya se han aprobado todas las características de C++17, así que es poco probable que sufra cambios importantes. Se hicieron centenares de propuestas para C++17.

¿Cuáles de estas características se añadieron a C++ en C++17?

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82

1 Answers1

12

Características del lenguaje:

Plantillas y Código Genérico

Deducción del argumento de plantilla para clases plantilla.

Documento técnico p0091r2.

Supongamos que tenemos las siguientes definiciones:

std::vector<int> vi1 = { 0, 1, 1, 2, 3, 5, 8 };
std::vector<int> vi2;
std::mutex m;
unique_lock<std::mutex> ul(m, std::defer_lock);

template<class Func> class Foo() { 
public: 
    Foo(Func f) : func(f) {} 
    void operator()(int i) { 
        os << "Calling with " << i << std::endl;
        f(i); 
    } 
private: 
    Func func; 
    std::mutex mtx;
};

Antes del estándar C++17, los siguientes objetos se deben construir tal y como se muestra:

pair<int, double> p(2, 4.5);
auto t = make_tuple(4, 3, 2.5);
copy_n(vi1, 3, back_inserter(vi2));

// Virtualmente imposible pasar una lambda al constructor
// de una clase plantilla sin declarar la lambda
for_each(vi2.begin(), vi2.end(), Foo<???>([&](int i) { ...}));

lock_guard<std::mutex> lck(foo.mtx);
// Notacion del documento tecnico N4470
lock_guard<std::mutex, std::unique_lock<std::mutex>> lck2(foo.mtx, ul);

auto hasher = [](X const & x) -> size_t { /* ... */ };
unordered_map<X, int, decltype(hasher)> ximap(10, hasher);

Si permitimos que el compilador deduzca los argumentos para los constructores de clases plantilla, podemos replazar el código anterior con:

pair p(2, 4.5);
tuple t(4, 3, 2.5);
copy_n(vi1, 3, back_insert_iterator(vi2));

// Ahora esto es facil, en lugar de virtualmente impossible
for_each(vi.begin(), vi.end(), Foo([&](int i) { ...}));

auto lck = lock_guard(foo.mtx);
lock_guard lck2(foo.mtx, ul);

// NOTA: la deducción de argumentos de plantilla deduce
// los argumentos no explícitos
unordered_map<X, int> ximap(10, [](X const & x) -> size_t { /* ... */ });

Declarar argumentos de plantilla no-tipo con auto.

Documento técnico p0127r1.

El tipo de un parámetro de plantilla no-tipo debe ser especificado explícitamente, esto provoca una verbosidad innecesaria y reduce la flexibilidad de aquellas plantillas que se pretende que reciban argumentos constantes de cualquier tipo:

template <typename T, T v> struct S { }; // Definicion
S<decltype(x), x> s;                     // Instanciacion

El ejemplo usa decltype para obtener el tipo de x (una constante en tiempo de compilación) antes de pasar ambos parámetros a S. Lo ideal sería modificar la declaración de S de manera que el tipo de x no fuera requerido:

S<x> s; // Instanciacion deseada

Esto se consigue permitiendo el uso de auto en la lista de parámetros de plantilla:

template <auto v> struct S; // Deduce el tipo de v

Permitir la evaluación constante de todos los argumentos de plantilla no-tipo.

Documento técnico n4198.

Antes de C++17, C++ permite los siguientes parámetros de plantilla no-tipo:

  • Cualquier Tipo Integral o Enumerado que sea una expresión constante.
  • Punteros que apunten a objetos en memoria estática, funciones con enlazado o expresiones constantes que se evaluen como puntero nulo.
  • Referencias a objetos en memoria estática o funciones con enlazado.
  • Punteros a miembros &X::y o expresiones constantes que se evaluen como puntero-a-miembro nulo.

A partir de C++17 los parámetros de plantilla no tipo pueden ser:

  • Cualquier Tipo Integral o Enumerado que sea una expresión constante.
  • Punteros que apunten a un objeto completo en memoria estática, una función o un puntero nulo.
  • Referencias a un glvalue referenciando a un objeto completo en memoria estática o una función.
  • Cualquier expresión constante de puntero a miembro.
  • std::nullptr_t.

Permitir typename en parámetros-plantilla (template template)

Documento técnico n4051

Es obligatorio usar class para el tipo de un parámetro-plantilla:

template<template<typename> class X> struct S; // class es requerido

A partir de C++17 también se podrá:

template<template<typename> typename X> struct S; // Se puede usar class o typename

Expresiones plegables1.

Documento técnico n4295.

Una expresión plegable, pliega un paquete de parámetros de plantilla sobre un operador binario plegable.

Los operadores plegables son:

+  -  *  /  %  ^  &  |  ~  =  <  >  <<  >>
+=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=
==  !=  <=  >=  &&  ||  ,  .*  ->*
  • Una expresión del tipo (... operador expresión) en que op es un operador plegable se conoce como pliegue unario a la izquierda.
  • Una expresión del tipo (expresión operador ...) en que op es un operador plegable se conoce como pliegue unario a la derecha.

Los pliegues unarios a izquierda y derecha se conocen como pliegues unarios. En un pliegue unario, la expresión debe contener un paquete de parámetros sin expandir.

  • Una expresión del tipo (expresión1 operador1 ... operador2 expresión2) en que operador1 y operador2 son operadores plegables se conoce como pliegue binario.

En un pliegue binario, operador1 y operador2 deben ser el mismo operador plegable y expresión1 o expresión2 deben contener un paquete de parámetros sin expandir mas no ambas. Si expresión2 contiene un paquete de parámetros sin expandir, la expresión se conoce como pliegue binario a izquierda. Si expresión1 contiene un paquete de parámetros sin expandir, la expresión se conoce como pliegue binario a derecha.

La instanciación de una expresión plegable produce:

  • ((Expr1 operador Expr2) operador ...) operador ExprN para pliegues unarios a la izquierda.
  • Expr1 operador (... operador (ExprN-1 operador ExprN)) para pliegues unarios a la derecha.
  • (((Expr operador Expr1) operador Expr2) operador ...) operador ExprN para pliegues binarios a la izquierda.
  • Expr1 operador (... operador (ExprN-1 operador (ExprN operador Expr))) para pliegues binarios a la derecha.

En cada caso:

  • operador es el operador plegable
  • N es el número de elementos en la expansión del paquete de parámetros.
  • ExprX es generado al instanciar el patrón y reemplazar cada parámetro del paquete con el X-ésimo elemento.

Para una expresión plegable binaria, Expr es generado al instanciar la expresión que no contiene un paquete de parámetros sin expandir:

template<typename... Args>
bool todos(Args... args) { return (args && ...); }

bool b = todos(true, true, true, false);

Al instanciar la plantilla todos obtendríamos:

bool todos(bool e1, bool e2, bool e3, bool e4) { return ((e1 && e2) && e3) && e4; }

Nuevas normas de deducción para auto con listas entre llaves.

Documento técnico n3922

En C++17 cambian las normas de deducción de tipos (auto) para las deducciones en que intervienen listas de elementos.

Para la inicialización por copia de lista:

  • La deducción de tipo de auto deduce std::initializer_list si los tipos en la lista son idénticos o en caso contrario será un error de compilación.

Para la inicialización por lista:

  • En listas con un sólo elemento, auto deducirá el tipo de ese elemento; previamente a C++17 deducía una std::initializer_list de un elemento.
  • En listas con más de un elemento, auto no deducirá, será un error de compilación.

Ejemplo:

auto a = { 1, 2 };   // deduce std::initializer_list<int> de dos elementos
auto b = { 3 };      // deduce std::initializer_list<int> de un elemento
auto c{ 3 };         // deduce int, antes deducia std::initializer_list<int> de un elemento

if constante.

Documento técnico p0128r1.

Un if cuya condición se evalúa en tiempo de compilación y la rama no tomada se descarta de la compilación, es útil para simplificar ciertos códigos:

void funcion()
{
    // Se requiere la funcion vacia para eliminar la recursion
}

template <class T> 
void funcion(T&& elemento)
{
    // Hacer cosas con el elemento
}

template <class T, class... Parametros>
void funcion(T&& cabeza, Parametros&&... cola)
{
    funcion(cabeza);
    funcion(cola...); // Finaliza la recursion cuando cola... es una lista vacia
}

Con if constante, la funcion vacia no es necesaria:

template <class T> 
void funcion(T&& elemento)
{
    // Hacer cosas con el elemento
}

template <class T, class... Parametros>
void funcion(T&& cabeza, Parametros&&... cola)
{
    funcion(cabeza);
    constexpr if (sizeof...(cola))
    {
        // No se genera esta parte del codigo si cola... es una lista vacia
        funcion(cola...);
    }
}

Lambdas

Lambdas expresión constante

Documento técnico n4487.

Antes de C++17 estaba prohibido que una lambda o su instancia (closure) formara parte de una expresión constante; esta restricción era inconsistente (ya que los funtores tradicionales no disponen de ella) e innecesaria:

// Este codigo es valido en C++17 pero incorrecto en estandares previos
constexpr auto L = [](int i) { return i; }; // Correcto en C++17

auto L2 = [] { return 0; };
constexpr int I = L2(); // Correcto en C++17

Capturar *this en lambdas

Documento técnico p0018r3.

Las lambdas declaradas en una función miembro no estática, capturan this implícita o explícitamente para acceder a las variables del objeto a través del puntero this; por este motivo la captura del contexto del objeto por copia o por referencia son iguales:

struct S {
    int x ;

    void f() {
        // Ambas lambdas son identicas pese a que su captura sea diferente:
        auto a = [&]() { x = 42 ; } // El acceso a x se transforma en (*this).x = 42
        auto b = [=]() { x = 43 ; } // El acceso a x se transforma en (*this).x = 43
    }
};

Esto provoca problemas en programación asíncrona pues puede provocar el uso de punteros a memoria que ha dejado de ser válida:

class Tarea {
private:
    int valor ;
public:
    Tarea() : valor(42) {}
    std::future generar()
    { return std::async( [=]()->int{ return valor ; }); }
};

std::future foo()
{
    Tarea tmp ;
    // La closure asociada con el std::future devuelto
    // tiene un puntero this implicito.
    return tmp.generar();
    // El puntero this de la closure deja de ser valido
    // al finalizar la funcion, despues del return
}

int main()
{
    std::future f = foo();
    f.wait();
    // El valor del futuro no es valido porque
    // el objeto que lo genero ya ha sido destruido
    assert( 42 == f.get() );
    return 0 ;
}

Pudiendo capturar *this soluciona el problema.

Atributos

Los Atributos fueron añadidos en C++11 y C++17 añade nuevos atributos.

Atributos [[fallthrough]], [[nodiscard]] y [[unused]]

Documento técnico p0068r0.

El atributo [[fallthrough]] se usa como una instrucción, la instrucción [[fallthrough]] se pone justo antes de una etiqueta case en un switch y su objetivo es marcar que la ejecución caerá hasta la próxima etiqueta case de manera intencionada:

switch (c) {
    case 'a':
        f(); // WARNING: falta break
    case 'b':
        g();
        [[fallthrough]]; // Correcto
    case 'c':
        h();
}

El atributo [[nodiscard]] puede usarse sobre tipos o sobre funciones; si ha sido marcado así y el tipo o el retorno de función no es usado en el código, se generará una alarma de compilación:

[[nodiscard]] int funcion();
void metodo() {
    funcion(); // WARNING: valor de retorno de una funcion nodiscard es descartado.
}

[[nodiscard]] struct S { ... };
S crear_S();

void funcion() {
    crear_S(); // WARNING: valor de retorno de un type nodiscard es descartado.
}

El atributo [[unused]] marca una entidad que por algún motivo no se usa. Si el compilador fuese a marcar dicha entidad con una alarma, no lo hará si el atributo es usado:

// Una de las dos funciones no se usa dependiendo de UNA_CONDICION
static void funcion1() { ... }
static void funcion2() { ... } // warning: funcion2 no se usa

void iface() {
#ifdef UNA_CONDICION
    funcion1();
#else
    funcion2();
#endif
}

Este atributo no evita que el compilador compile la entidad marcada, así que ambas funciones serán compiladas pero ninguna será marcada con una alarma.

[[unused]] static void funcion1() { ... }
[[unused]] static void funcion2() { ... }

Atributos en espacios de nombres y enumerados

Documento técnico n4266.

A partir de C++17 se permite marcar espacios de nombres y enumerados con atributos.

Sintaxis

Variables inline.

Documento técnico n4424.

C++ requiere que las funciones y variables extern tengan exáctamente una definición en el programa, este requerimiento se relajó para las funciones mediante el calificador inline. Las funciones inline se pueden definir en varios puntos pero todas sus definiciones deben ser la misma; lo mismo sucede para las funciones y variables instanciadas desde plantillas. Antes de C++17 no existía nada parecido para variables no plantilla pesea a que no es extraño necesitar un objeto global y único sin que este tenga que estar definido en una única unidad de traducción.

Definición de espacios de nombres anidados

Documento técnico n4230.

A partir de C++17 este código:

namespace A::B::C // Error en estandares previos a C++17
{
// codigo
}

Será equivalente a:

namespace A
{
namespace B
{
namespace C
{
// codigo
}
}
}

Extender static_assert.

Documento técnico n3928.

A partir de C++17 el segundo parámetro de static_assert es opcional:

static_assert ( true  != true ); // Correcto en C++17, incorrecto en estandares anteriores
static_assert ( false != false , "Falso debe ser falso" );

Retorno múltiple y control de flujo más claros.

Enlazados estructurados1

Documento técnico p0217r2.

C++17 ofrece la posibilidad de declarar múltiples variables inicializadas desde una tupla o estructura:

std::tuple<T1,T2,T3> crear_tupla(/*...*/) { /*...*/ return { a, b, c }; }
auto [ x, y, z ] = crear_tupla(); // x deduce tipo T1, y deduce tipo T2, z deduce tipo T3

// iterador deduce tipo typeof(mapa)::iterator, insertado deduce bool
const auto [iterador, insertado] = mapa.insert({"clave", valor});

struct S { int x; volatile double y; };
S f();
// x deduce const int, y deduce const volatile double
const auto [ x, y ] = f();

Inicializador en instrucción if.

Documento técnico p0305r0.

C++ permite declarar e inicializar una variable en la instrucción if, si dicha variable es evaluable como condición booleana, se entrará en la rama verdadera de la instrucción; pero esto no es posible cuando la variable no es evaluable como booleana:

if (int no_multiplo_de_3 = x % 3)
{
std::cout << no_multiplo_de_3 << " no es multiplo de 3\n";
}

// Error! decltype(mapa)::iterator no es evaluable como condicion
if (auto iterador = mapa.find("clave"))
{ ... }

C++17 permite separar la declaración de la variable y su evaluación en la instrucción if:

if (auto iterador = mapa.find("clave"); iterador != mapa.end())
{
    std::cout << "clave existe en el mapa\n";
}

// Combinado con enlazados estructurados
if (const auto [iterador, insertado] = mapa.insert({clave, valor}); insertado)
{
    std::cout << clave << " ha sido insertada en el mapa\n";
}

Generalización del bucle for de rango.

Documento técnico p0184r0.

Permite que el tipo del iterador begin sea diferente al tipo del iterador end.

Miscelánea

Literales hexadecimales de números en coma flotante

Documento técnico p0245r1.

Existe una notación científica en base-2 para valores numéricos en coma flotante en base hexadecimal: 0x3.ABCp-10. La mantisa se expresa en hexadecimal y el exponente en decimal interpretado con respecto a base 2. El menor valor de precisión simple expresable estándar IEEE-754 sería 0x1.0p-126.

Asignación dinámica de memoria para datos alineados.

Documento técnico p0035r1.

C++11 añadió la posibilidad de especificar el alineamiento para objetos, peró no especificó ningún mecanismo para asignar memoria dinámica correctamente a estos objetos alineados, en C++17 se añaden sobrecargas a los operadores new y delete para especificar la alineación:

void* operator new(std::size_t size, std::align_val_t alignment);
void operator delete[](void* ptr, std::align_val_t alignment) noexcept;
void operator delete[](void* ptr, std::align_val_t alignment, std::size_t size) noexcept;

Elisión de copia garantizada.

Documento técnico p0135r0.

En C++17 se garantiza que ciertas copias serán elididas, en particular: cuando el objeto a elidir es un valor temporal.

Refinamiento del orden de evaluación de expresiones para C++ idiomático.

Documento técnico p0145r2.

Garantías de progreso para la especificación técnica de Paralelismo V2.

Documento técnico p0299r0.

Literales de caracteres UTF8.

Documento técnico n4267.

C++11 añadió los literales de cadenas de texto UTF:

auto utf8     = u8"UTF8"; // Literal de cadena de texto codificado en UTF8
auto utf16    = u"UTF16"; // Literal de cadena de texto codificado en UTF16
auto utf32    = U"UTF32"; // Literal de cadena de texto codificado en UTF32

C++17 añade los literales de caracter de texto UTF:

auto utf8     = u8'8'; // Literal de caracter codificado en UTF8
auto utf16    = u'F';  // Literal de caracter codificado en UTF16
auto utf32    = U'X';  // Literal de caracter codificado en UTF32

Especificación de excepciones como parte del tipo de la función.

Documento técnico p0012r1.

La especificación de excepciones no forma parte del tipo de la función en estándares previos a C++17:

void f_throw() throw(int) {}
void f_no_throw() {}

using void_throw = void() throw(int);
// Presuntamente un puntero a funcion void que puede lanzar excepciones
void_throw *p;

p = f_throw;    // Correcto
p = f_no_throw; // Correcto tambien!

A partir de C++17 se generaría un error:

p = f_throw;    // Correcto
p = f_no_throw; // ERROR: No se puede convertir void() a void() throw(int)

Verificación estática de cabeceras.

Documento técnico p0061r1.

La instrucción __has_include permitirá comprobar si una cabecera está disponible

#if __has_include(<optional>)
#  include <optional>
#  define EXISTE_OPTIONAL 1
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>
#  define EXISTE_OPTIONAL 1
#  define EXPERIMENTAL_OPTIONAL 1
#else
#  define EXISTE_OPTIONAL 0
#endif

Correcciones sobre conversiones de arreglos a punteros.

Documento técnico n4261.

Correcciones sobre constructores heredados.

Documento técnico p0136r1.

Novedades de STL

Tipos de datos

std::variant Union de tipado seguro.

Documento técnico p0088r2.

std::optional Objeto que puede contener un valor arbitrario, o tal vez no.

Documento técnico n4480.

std::any Objeto que puede contener diferentes tipos.

Documento técnico n4480.

std::string_view Referencia a cadenas de texto.

Documento técnico n4480.

std::shared_mutex mutex compartido.

Documento técnico n4480.

Invoke

std::invoke Objeto de llamada genérica.

Documento técnico n4169.

Puede llamar cualquier objeto llamable: puntero a función, función, función miembro... con una sintaxis unificada.

std::apply Llama una función con una tupla con parámetros.

Documento técnico n4480.

Especificación técnica de Sistema de Archivos v1

Documento técnico p0218r0.

Especificación técnica de Paralelismo v1

Documento técnico n4071.

Especificación técnica de Fundamentos de Librería v1

Documento técnico p0220r1.

eferion
  • 49,291
  • 5
  • 30
  • 72
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82