31

Desde el comité de estándares de se puso como meta aprobar un nuevo estándar cada 3 años. Así ha sido hasta la fecha y hemos tenido los siguientes estándares:

La estandarización se divide en cuatro grupos de trabajo:

  • Núcleo (Core): Mantenimiento del lenguaje y estandarización de nuevas propuestas.
  • Evolución: Desarrollo de nuevas características para el lenguaje.
  • Librería: Mantenimiento de la librería estándar y estandarización de nuevas propuestas.
  • Evolución de librería: Desarrollo de nuevas características para la librería estándar.

En Julio de 2019 se consideró el estándar C++20 como cerrado. Esto implica que no se le pueden añadir nuevas características (aunque sí se podría eliminar alguna si se detectasen problemas) y las características aprobadas están siendo revisadas para posibles mejoras o correcciones menores.

En esta última reunión, se consideró que la propuesta de Contratos2 (documento técnico P0542R4) no sería incorporada en C++20 y se retrasa a C++23.

Vamos a listar algunas de las principales características que incorporará C++20.



1Debido a algún extraño despiste, escribí dos hilos respecto C++17.

2Según la Wikipedia el contrato es:

El diseño por contrato es una metodología para el diseño e implementación de aplicaciones y componentes popularizada por el lenguaje de programación Eiffel. Consiste en considerar los elementos de diseño como participantes de una relación similar al contrato de negocios. Así, se pueden diseñar los componentes asumiendo que se cumplirán ciertas condiciones de entrada (pre-condiciones), mientras que se deberán garantizar ciertas condiciones de salida (post-condiciones), así como la invariante de clase (propiedades que se mantienen invariantes a pesar del procesamiento realizado por el componente).

Los Contratos C++ seguirán la sintaxis de los atributos (añadidos en C++11):

[[contrato modificador: expresión-condicional]]

Los contratos han sido movidos a C++23, lo expuesto a continuación está sujeto a cambios: Los contratos disponibles son:

  • expects: Un contrato de tipo expects es una pre-condición. Indica lo que una función espera de sus argumentos y se espera que dicho contrato se mantenga durante toda la llamada la función.
  • ensures: Un contrato de tipo ensures es una post-condición. Indica el estado que se espera que tengan los objetos pasados a una función (o el retorno de la misma) al finalizar la llamada.
  • asert: Un contrato de tipo assert es una aserción. Indica una condición que se espera que sea satisfecha en el lugar en que aparece el contrato.

Cuando un contrato no se cumpla, se hará una llamada a una función con la siguiente firma:

void(const std::contract_violation &);

El objeto std::contract_violation contendrá información del incumplimiento del contrato como dónde (archivo, línea, función) se ha incumplido.

Se pueden aplicar modificadores a los contratos, los modificadores afectarán a cómo se genera el código con contratos y si los contratos son o no verificados en tiempo de ejecución. Los modificadores disponibles son:

  • axiom: Se espera que la verificación de las condiciones de este contrato no tenga coste, no se evalúa en tiempo de ejecución.
  • default: Se espera que la verificación de las condiciones de este contrato sea poco costosa.
  • audit: Se espera que la verificación de las condiciones de este contrato sea costosa.

Los contratos que serán verificados en tiempo de compilación contienen código que será compilado, se puede especificar el nivel con que se quiere compilar cada unidad de traducción y el comportamiento resultante será:

  • Por defecto: Verifica (compila) los contratos marcados como default (los contratos que no tengan modificador se consideran default).
  • Auditoría: Verifica (compila) los contratos marcados como audit y default.
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
  • 2
    Hay alguna novedad sobre una posible librería estándar para trabajar con fechas del calendario? – Xam Jul 14 '18 at 19:20
  • 2
    La hay: el documento técnico [P0355R5](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0355r5.html) trata exactamente de eso. – PaperBirdMaster Jul 17 '18 at 08:14

3 Answers3

28

Núcleo.

Conceptos.

Los conceptos son una mejora al sistema de plantillas de C++, son un tipo especial de función evaluable en tiempo de compilación que debe devolver verdadero para considerar que el concepto se cumple. Su función es controlar las características que deben cumplir los parámetros de una plantilla. Pero su funcionalidad no queda limitada al ámbito de las plantillas, también pueden ser usados en otros contextos.

Los conceptos son una funcionalidad de C++ muy esperada que lleva 10 años en desarrollo (quedó fuera de C++11 por falta de tiempo, fuera de C++14 por considerarse incompleta y fuera de C++17 por falta de consenso).

Ejemplo.

Podemos definir un concepto de la siguiente manera:

template <typename tipo_t>
concept bool es_sumable()
{
    return requires(tipo_t a, tipo_t b)
    {
        { a + b } -> tipo_t;
    };
};

El concepto anterior se cumple si la expresión a + b es posible y su tipo es tipo_t, entonces podemos usar el concepto como parámetro:

auto suma(const es_sumable &a, const es_sumable &b) { return a + b; }

Y la función suma sólo aceptará tipos que cumplan con el concepto es_sumable. Se puede usar también como variable:

es_sumable resultado = "Hola "s + "mundo!"s;

La variable resultado tendrá que cumplir es_sumable o el código no compilará. Por último, se puede requerir que una plantilla cumpla con uno o varios conceptos:

template <typename tipo_t>
concept bool entero() { return std::is_integral<tipo_t>::value; }

template <typename tipo_t>
concept bool de_4_bytes() { return sizeof(tipo_t) == 4; }

template <typename T>
T dobla(const T &a) requires entero<T>() && de_4_bytes<T>()
{
    return a + a;
}

En la función anterior, el parámetro plantilla T tiene que ser un entero de 32bits, en caso de no serlo se mostrará un error claro y conciso:

auto dobla(111ull);

note:   constraints not satisfied
T dobla(const T &a) requires entero<T>() && de_4_bytes<T>()
  ^~~~~
note: within 'template<class tipo_t> concept bool de_4_bytes() [with tipo_t = long long unsigned int]'
concept bool de_4_bytes()
             ^~~~~~~~~~
note: 'sizeof (long long unsigned int) == 4' evaluated to false

Inicializadores por nombre en agregados (DT P0329R0).

Propuesta para permitir especificar los nombres en la inicialización de objetos de tipo agregado, como ya se permite en C (desde C99).

Ejemplo.

Suponiendo que tenemos el siguiente objeto:

struct punto { float x, y, z; };

La siguiente inicialización sería correcta en a partir de C++20:

punto p { .x{}, .y{}, .z = 100.f };

Hay ciertas diferencias respecto a C99 que se deben tener en cuenta:

  • Se deben inicializar todos los miembros:

    punto p { .x = .0f, .z = 100.f }; // Correcto en C, incorrecto en C++
    
  • Los miembros deben estar en el orden de definición:

    punto p { .z = 100.f, .y = .0f, .x = .0f }; // Correcto en C, incorrecto en C++
    
  • Los miembros no deben estar duplicados:

    punto p { .x = 100.f, .x = .0f }; // Correcto en C, incorrecto en C++
    
  • No se permite la inicialización de formación:

    struct punto { float v[3]; };
    p { .v[1] = 100.f }; // Correcto en C, incorrecto en C++
    
  • No se permite la inicialización anidada:

    struct punto { float x, y, z; };
    struct linea { punto a, b; };
    linea l { .a.x = 100.f }; // Correcto en C, incorrecto en C++
    

Lambdas plantilla (DT P0428R2).

Desde C++14 el lenguaje permite Lambda Genéricas; pero pese a que hacen las Lambdas de C++ más útiles, las Lambda genéricas no ofrecen toda la flexibilidad que podría esperarse, hasta el punto que en algunos contextos sigue siendo más útil utilizar una función plantilla antes que una Lambda Genérica. Este documento técnico propone la siguiente nueva sintaxis para Lambdas:

[captura(s)]<parámetro(s) plantilla>(parámetro(s) Lambda){ código; }

Es decir, se añaden los paréntesis angulares (< y >) a la Lambda para poder gestionar parámetros de plantilla.

Ejemplo.

Lambda que trabaja sólo con std::vector versión Genérica:

auto g = [](auto v)
{
    static_assert(std::is_std_vector<decltype(v)>::value, "necesito vector!");
    // Obtenemos el tipo almacenado en el vector
    using T = typename decltype(vector)::value_type; 
    // ... código ...
};

Versión plantilla:

auto p = []<typename T>(std::vector<T> v)
{
    // ... código ...
}; 

Lambda que llama una función estática del tipo pasado y accede a tipos anidados versión Genérica:

auto g = [](const auto &t)
{
    /* Necesitamos decay porque si no deduce referencia (&)
    y no podríamos usar el operador de contexto (::) para
    acceder a funciones estáticas o tipos anidados. */
    using T = std::decay_t<decltype(t)>;
    T copia = t;
    T::f_estatica();
    using Iterator = typename T::iterator;
    // ... código ...
};

Versión plantilla:

auto p = []<typename T>(const T &t)
{
    /* Obtenemos el tipo como parámetro de plantilla, esto
    nos evita complicar el código con decay. */
    T copia = t;
    T::f_estatica();
    using Iterator = typename T::iterator;
    // ... código ...
}; 

Comparación a tres bandas (documento técnico P0515R3).

Esta propuesta es conocida con el pseudónimo de "El operador nave espacial" por el parecido del operador propuesto (operator <=>) con un Tie-Fighter:

La propuesta sugiere añadir un operador nuevo al lenguaje que realice una comparación entre objetos pudiendo obtener tres resultados distintos, para la comparación a tres bandas a <=> b se obtendría:

  • Un valor menor a cero si a fuese menor que b.
  • Un valor equivalente a cero si a y b fuesen equivalentes.
  • Un valor mayor a cero si a fuese mayor que b.

La comparación a tres bandas permitiría reducir la cantidad de código necesario para permitir que un dato sea estrictamente ordenable, que requiere varias comparaciones que suelen implementar en base a las anteriores para ahorrar código:

struct coordenada { int x, y; };

bool operator==(const coordenada &a, const coordenada &b) { return a.x == b.x && a.y == b.y; }
bool operator< (const coordenada &a, const coordenada &b) { return a.x < b.x || (a.x == b.x && a.y < b.y); }
bool operator!=(const coordenada &a, const coordenada &b) { return !(a==b); }
bool operator<=(const coordenada &a, const coordenada &b) { return !(b<a); }
bool operator> (const coordenada &a, const coordenada &b) { return b<a; }
bool operator>=(const coordenada &a, const coordenada &b) { return !(a<b); } 

La propuesta sugiere permitir al compilador generar por defecto la comparación a tres bandas, de hacerse así realizaría una comparación a tres bandas miembro a miembro de manera recursiva entre los operadores del operador.

Ejemplo.

Comparación a tres bandas (generada por el compilador) del objeto del código anterior:

struct coordenada
{
    int x, y;
    auto operator<=>(const coordenada &) const = default; 
};

El tipo de retorno será auto porque el tipo retornado es dependiente del compilador, la única restricción que debe seguir es que se pueda comparar contra cero (mayor, menor o igual a cero).

for de rango con inicializador (documento técnico P0614R1).

En C++11 se añadió el bucle for de rango con la siguiente sintáxis:

for (declaración : expresión) {
    Código
}

Permite crear código más fácil de entender y mantener, pero carece de la flexibilidad del bucle for tradicional al no permitir inicializadores. La propuesta consiste en flexibilizar el bucle for de rango con un inicializador:

for (inicialización; declaración : expresión) {
    Código
}
Ejemplo.

Obtenemos un objeto temporal del cual iteramos una de sus propiedades internas:

for (Coche c = fabricar_coche(); auto &rueda : c.ruedas()) {
    std::cout << comprobar_presion(rueda) << '\n';
}

Lambdas sin estado asignables y construibles por defecto (DT P0624R2).

Una lambda sin capturas recibe el nombre de "Lambda sin estado", este tipo de lambdas es implícitamente convertible a puntero a función:

using funcion_void = void();
función_void *fv = []{ std::cout << "Hola mundo!\n"; };

Pero en algunas situaciones no se comportan como punteros a función. Los punteros a función son copiables, asignables y construibles mientras que las lambdas sin estado no lo son.

Ejemplo.

Esta propuesta hace que las lambda sin estado se comporten como punteros a función:

using funcion_void = void();
void a(){}
void b(){}
void f(funcion_void *){}

funcion_void *fv; // Correcto, construcción por defecto.
fv = a;           // Correcto, asignación.
fv = b;           // Correcto, reasignación.
f(fv);            // Correcto, copia.

auto lambda = []{};
decltype(lambda) l;          // Construible por defecto? Error (Correcto en C++20).
decltype(lambda) m = lambda; // Asignable? Error (Correcto en C++20).
decltype(lambda) m = l;      // Copiable? Error (Correcto en C++20).

Permitir la expansión de paquetes de parámetros en la captura de lambdas (DT P0780R0)

En una lambda se pueden capturar los parámetros por copia:

template <typename ... paquete_de_parametros>
void f(paquete_de_parametros ... parametros)
{
    auto lambda = [parametros ...]{};
//                 ^^^^^^^^^^^^^^ <--- captura por copia.
}

Pero el estándar previo a C++20 prohíbe explícitamente expandir paquetes de parámetros en las capturas.

Ejemplo.

C++20 levanta esta prohibición:

template <typename ... paquete_de_parametros>
void f(paquete_de_parametros ... parametros)
{
    auto lambda = [std::move(parametros) ...]{};
//                 ^^^^^^^^^^^^^^^^^^^^^ <--- Expande con std::move
}

Literales de cadena de caracteres como parámetros no-tipo en plantillas (DT P0424R2).

El estándar previo a C++20 establece explícitamente que los literales de cadena de caracteres no pueden ser usados como parámetros de plantilla:

template <auto T> void f() {}

f<"Hola">(); // Error!
f<"Hola">(); // Error!

El motivo de esta limitación es que dos literales de texto iguales (como en el ejemplo anterior) no tienen garantías de ser el mismo literal (se almacenan como punteros) y en consecuencia no hay manera de saber si dos funciones con el mismo literal de texto son la misma. En C++20 se permite la verificación del parámetro literal de texto en tiempo de compilación pudiendo comprobar si dos literales de texto tienen el mismo contenido, esto permite que los literales de texto puedan ser usados como parámetros no-tipo en plantillas.

Macros de verificación de funcionalidades (DT P0941R1).

Si el compilador dispone de una funcionalidad determinada, ésta podrá ser verificada usando la macro __has_cpp_attribute. La macro recibirá un único parámetro que será un identificador asociado a una funcionalidad determinada.

Ejemplo.

Si el compilador dispone del bucle for de rango (C++11) lo usamos, usamos el bucle tradicional en caso contrario:

int datos[100];

#if __has_cpp_attribute(__cpp_range_based_for)
    for (int &i : datos)
    {
        i = 0;
    }
#else
    for(int *i = datos; i != datos + 100; ++i)
    {
        *i = 0;
    }
#endif

Explícito condicional explicit(bool) (documento técnico P0892R2).

En el estándar C++11 se permitió controlar las conversiones implícitas al construir o convertir objetos al permitir marcar como explícitos los constructores u operadores. En C++20 se amplía esta funcionalidad pudiendo pasar un parámetro al especificador explicit para hacer que éste se aplique condicionalmente.

Ejemplo.

El objeto O tiene un constructor explícito cuando el tipo usado para construirlo sea de mayor tamaño que el tipo almacenado, constructor implícito en caso contrario:

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

    template <typename U>
    explicit(sizeof(U) > sizeof(T))
    O(U u) :
        t{static_cast<T>(u)} {}
};

La palabra clave explicit esperará una expresión convertible a booleano a partir de C++20, así que habrá que corregir algunas líneas de código:

struct S
{
    explicit(true) S(int) {}          // Constructor explícito
    explicit(false) S(std::string) {} // Constructor implícito
};

Permitir que las llamadas a funciones virtuales sean constexpr (DT P1064R0).

Las llamadas a funciones virtuales están prohibidas en expresiones constantes en estándares previos a C++20.

Ejemplo.

El compilador conoce los tipos implicados en algunas llamadas virtuales y podría resolver la llamada en tiempo de compilación.

struct X1
{
    constexpr virtual X1 const* f() const { return this; }
//  ~~~~~~~~~~~~~~~~~ <--- Error! Correcto a partir de C++20.
};

struct Y
{
    int m = 0;
};

struct X2: public Y, public X1
{
    constexpr virtual X2 const* f() const { return this; }
//  ~~~~~~~~~~~~~~~~~ <--- Error! Correcto a partir de C++20.
};

constexpr X1 x1;
static_assert( x1.f() == &x1 );
//                ~~ <--- 'f' es una función virtual, no se puede usar 
//                         en una verificación estática, salvo en C++20 o superior

Funciones Inmediatas (DT P1073R2).

A partir de C++11 se puede usar la palabra clave constexpr para marcar valores o funciones que podrían ser válidas dentro de una expresión constante (expresión calculada en tiempo de compilación). Las funciones marcadas como constexpr pueden también ser llamadas en tiempo de ejecución.

Esta propuesta añade la palabra clave consteval para marcar valores o funciones que obligatoriamente deben ser evaluadas en tiempo de compilación y producir un error en caso contrario.

Las funciones consteval reciben el nombre de función inmediata porque la función no se compila para ser ejecutada si no que se evalúa en tiempo de compilación y se sustituye su llamada por el resultado de la evaluación, una función inmediata no tiene por qué aparecer en el binario compilado.

Ejemplo.

Las funciones inmediatas provocan un error de compilación si no se pueden calcular en tiempo de compilación:

consteval int cubo(int n) {
  return n*n*n;
}
// Correcto: 'cubo' se llama con un literal, es calculable en tiempo de compilación
constexpr int v = cubo(3);
int tres = 3;
// Error: 'cubo' se llama con una variable, NO es calculable en tiempo de compilación
int w = cubo(tres);

constinit (DT P1143R2).

El lenguaje C++ no especifica el orden en que las variables estáticas deben ser inicializadas, dejando esta decisión en manos de los compiladores. Esto provoca que las variables estáticas con inicializadores dinámicos provoquen errores difíciles de rastrear cuando unas dependen de otras (esto se conoce como Static Initialization Order Fiasco). Las variables con inicializadores constantes evitan este problema, ya que pueden inicializarse en tiempo de compilación y se pueden usar de manera segura para inicializar otras variables.

Para garantizar que una variable se inicializa en tiempo de compilación, se debe obligar al compilador a que la inicialización sea constante, para ello se puede marcar una con constinit:

Ejemplo.

Tenemos una función (valor_dinamico) que calcula un valor en tiempo de ejecución y otra (misterio) que podría ser evaluada en tiempo de compilación o en tiempo de ejecución:

int valor_dinamico() { static int x = 0; return x++; }
constexpr int misterio(bool p) { return p ? 42 : valor_dinamico(); }

constinit const int a = misterio(true);  // Correcto.
constinit const int b = misterio(false); // Error

Si misterio no puede ser evaluada en tiempo de compilación, la expresión marcada con constinit falla.

using enumerado (DT P1099r5).

En C++11 se añadieron al lenguaje los enumerados-clase:

enum class Dia : unsigned { L, M, X, J, V, S, D };

Este nuevo tipo de enumerado no exporta sus símbolos al ámbito superior:

enum Vocal { a, e, i, o, u };
enum class Dia : unsigned { L, M, X, J, V, S, D };

int main()
{
    // Correcto, 'Vocal' exporta sus símbolos a este ámbito: 'Vocal::a' es accesible.
    std::cout << a << '\n';

    // Error, 'Dia' NO exporta sus símbolos a este ámbito: 'Día::D' NO es accesible.
    std::cout << static_cast<unsigned>(D) << '\n';

    return 0;
}
Ejemplo.

A partir de C++20 se permite importar los símbolos de un enumerado-clase mediante la cláusula using:

enum Vocal { a, e, i, o, u };
enum class Dia : unsigned { L, M, X, J, V, S, D };

int main()
{
    // Correcto, 'Vocal' exporta sus símbolos a este ámbito: 'Vocal::a' es accesible.
    std::cout << a << '\n';

    using enum Dia;
    // Correcto, 'Dia' exportó explícitamente sus símbolos a este ámbito: 'Dia::D' es accesible.
    std::cout << static_cast<unsigned>(D) << '\n';

    return 0;
}

Permitir conversiones a formaciones abiertas (DT P0388R4)

WIP.

Funciones miembro especiales condicionalmente triviales (DT P0848R3)

WIP.

Parcheando la deducción de parámetros plantilla de clase (DT P1021R5)

WIP.

Deprecar volatile (DT P1152R4)

WIP.

Gerard097
  • 191
  • 1
  • 3
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
  • La primer diferencia que se muestra entre C y C++20 en los Inicializadores por nombre en agregados es falsa. De https://es.cppreference.com/w/cpp/language/aggregate_initialization : "Si el número de cláusulas inicializador es menor que el número de miembros, los miembros restantes se inicializan mediante listas vacías, que realiza valor de inicialización. Si un miembro de un tipo de referencia es uno de estos miembros restantes, el programa está mal formada (referencias no pueden ser de valor inicializado)" – Gerard097 Jan 15 '20 at 01:34
  • Refiriéndome al mismo tema, es posible la inicialización anidada con la siguiente sintaxis: `Linea l { Punto{.x = 100.f} };` o `Linea l { {.x = 100.f}, {.x = 22.f, .y = 99.f, .z = 11.f} };` – Gerard097 Jan 15 '20 at 01:50
  • @Gerard097estás describiendo el comportamiento actual. Este hilo documenta el comportamiento propuesto. [aquí](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0329r0.pdf) puedes consultar la propuesta *P0329R0: Designated Initialization*. – PaperBirdMaster Jan 15 '20 at 08:06
  • De hecho en el documento que mencionas en la página 2 se menciona tal cual como lo describes, sin embargo en el ejemplo que se muestra en las páginas 5/6 de las reglas de inicialización se expresa claramente que los miembros directos no estáticos que se omitan serán inicializados utilizando su inicializador de miembros default o en caso de contar con uno utilizando {} – Gerard097 Jan 15 '20 at 23:21
  • @Gerard097 entonces, no se si he entendido tu primer comentario. Lo que propone *P0329R0: Designated Initialization* es que se puedan inicializar miembros de agregados por nombre (con ciertas restricciones) y los que no se inicialicen seguirán las normas ya establecidas. – PaperBirdMaster Jan 16 '20 at 08:16
2

Librería.

Extender <chrono> para manejar calendarios y zonas horarias (DT P0355R7).

Se incorpora a la librería de manejo de tiempo <chrono> utilidades para manejar calendarios y zonas horarias:

Ejemplo.
using namespace std::chrono;
auto date = 2016y/May/29;
std::cout << date << '\n'; // Muestra 2016-05-29 (cambia según configuración local).

Conversión estándar a bits (documento técnico P0476R2).

En programación a bajo nivel, suele ser necesario interpretar un dato como si fuera otro; en otras palabras: mantener la misma representación binaria pero obteniendo un dato de diferente tipo.

Normalmente se usa reinterpret_cast o type punning mediante uniones para conseguirlo, pero éstos métodos son propensos a errores, lo más seguro es usar un std::aligned_storage sobre el que copiar los datos, pero es incómodo y costoso.

La nueva cabecera <bit> proporciona herramientas para acceder, manipular y procesar tanto secuencias de bits como bits individuales de una forma menos propensa a errores y más flexible: asegura que los tamaños de los tipos de entrada y de salida coincidan, garantiza que ambos tipos sean copiables y hace que la expresión se calcule en tiempo de compilación cuando sea posible.

Librería de formato de texto (DT P0645R10).

Las herramientas de escritura de texto de C++ son difíciles de entender y usar, el encadenado de std::ostream y los formateos de <iomanip> en ocasiones se quedan cortos y no ofrecen una suficiente flexibilidad, en ocasiones se delega a printf por claridad o costumbre. Este documento técnico propone una alternativa a el formateo de texto con la flexibilidad de printf y la seguridad de std::ostream.

Ejemplo.

La librería de formato de texto acepta argumentos posicionales implícitos y explícitos:

std::cout << std::format("La cadena '{}' tiene {} caracteres\n", string, string.length()));
std::cout << std::format("{1} caracteres tiene la cadena '{0}'\n", string, string.length()));

Constantes matemáticas (DT P0631R8).

Este documento técnico propone una nueva cabecera <numbers> y espacio de nombres numbers. Se incluyen varias constantes matemáticas de uso común como e, π, inversa de π… en formato de variable plantilla:

template<typename T> constexpr T pi =3.14159265358979323846L;

Sufijos para ptrdiff_t y size_t (DT P0330R3)

El siguiente código provoca un error de compilación:

std::vector<int> v{0, 1, 2, 3};
for (auto i = 0, f = v.size(); i < f; ++i) {
    // …
}

La variable i se deduce como int mientras que f se deduce como el retorno de std::vector::size (que suele definirse como std::size_t), el siguiente código corrige el problema:

std::vector<int> v{0, 1, 2, 3};
for (std::size_t i = 0, f = v.size(); i < f; ++i) {
    // …
}

Pero pierde flexibilidad. Dado que no existen literales de tipo std::size_t se propone un sufijo:

Ejemplo.

El sufijo zu (size unsigned) soluciona el problema:

std::vector<int> v{0, 1, 2, 3};
for (auto i = 0zu, f = v.size(); i < f; ++i) {
    // …
}
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
1

Evolución.

Excepciones determinísticas sin coste: throw valores (DT P0709R4)

WIP.

using de enumerado (DT P1099R5)

WIP.

Soporte a la configuración de clases (DT P1112R2)

WIP.

constinit (DT P1143R2)

WIP.

Añadir texto informativo a [[nodiscard]] (DT P1301R4)

WIP.

Evolución de librería.

Integrar simd con algoritmos paralelos (DT P0350R3)

WIP.

Hacer que std::string y std::vector sean constexpr (DT P0980R1 y P1004R2)

WIP.




Librería de Conceptos (documento técnico P0898R2).

Los conceptos serán una herramienta de C++ para hacer verificaciones sobre los tipos de los parámetros en tiempo de compilación. Es una utilidad que se lleva desarrollando desde C++11 y aunque aún no ha sido incorporada al lenguaje hay varios compiladores que le dan soporte, lo que hace pensar que será aprobada en breve. Para evitar desarrollar conceptos desde cero se propone una librería de conceptos que ofrezca los más comunes.

Ejemplo.

Usamos el concepto Copyable para que una función sólo acepte tipos que se puedan copiar:

#include <concepts>

template <std::Copyable T>
T clonar(const T&)
{
    // ...
}

Clases como parámetros no-tipo en plantillas (documento técnico P0732R2).

C++ sólo permite datos que puedan ser evaluados en tiempo de compilación para verificar su unicidad como argumentos para parámetros no-tipo en plantillas. Esto limita el tipo de estos parámetros a enteros y punteros o referencias a datos o funciones con enlazado externo.

En otras palabras, se necesita garantizar que dada una plantilla con parámetro no-tipo, tiene que ser cierto que &P<a> == &P<b> cuando a == b, pero las clases de C++ no son por defecto comparables por igualdad ni el operador de igualdad forma parte de las funciones especiales que auto-genera el compilador. Así que este documento técnico propone usar el operador de comparación a tres bandas (apodado operador nave espacial <=>) ya que se ha propuesto que éste si sea auto-generado por el compilador.

Ejemplo.

Este código sería válido en C++20 pero no lo sería en estándares anteriores:

struct S{};

template <S s> struct T {};

T<S{}> t; // Instancia de S como parámetro no-tipo

Reflexión estática (documento técnico P0275R4).

Según la Wikipedia, la reflexión es:

La capacidad que tiene un programa para observar y opcionalmente modificar su estructura de alto nivel.

Un lenguaje con reflexión proporciona un conjunto de características disponibles en tiempo de ejecución que, de otro modo, serían muy difícilmente realizables en un lenguaje de más bajo nivel. Algunas de estas características son las habilidades para:

  • Descubrir y modificar construcciones de código fuente (tales como bloques de código, clases, métodos, protocolos, etc.) como objetos de "categoría superior" en tiempo de ejecución.
  • Convertir una cadena que corresponde al nombre simbólico de una clase o función en una referencia o invocación a esa clase o función.
  • Evaluar una cadena como si fuera una sentencia de código fuente en tiempo de ejecución.

Este documento técnico propone añadir utilidades de reflexión a C++, esta reflexión sería estática (disponible en tiempo de compilación), para ello se añadiría al lenguaje la palabra clave reflexpr que aplicada sobre una definición, devolverá un objeto que contenga información de la misma.

Ejemplo.

Podemos obtener el nombre de un parámetro así:

void f(char c, short s, int i, long l, float f, doublé d);

int main()
{
    auto metadatos_f = reflexpr(f);
    auto parametros_f = get_parameters_t<metadatos_f>;
    auto tercer_parametro_f = get_element_t<2, parametros_f>;

    std::cout << get_name_v<tercer_parametro_f>; // muestra 'i'

    return 0;
}

Usar [[nodiscard]] en la librería estándar (documento técnico P0600R1).

Este documento técnico propone añadir el atributo [[nodiscard]] en la librería estándar cuando no usar el valor de retorno:

  • Suponga un gran error (como fugas de memoria).
  • Sea propenso a generar problemas.

De momento se valora marcar como [[nodiscard]] std::async(), std::launder, std::allocate y empty en contenedores.

Flujo de salida con sincronización (documento técnico P0053R7).

El estándar garantiza que los flujos de salida de datos no producirán condiciones de carrera, pero no garantiza que enviar datos a esos flujos produzca un efecto perceptible; para que esto sea así se requiere un mecanismo de sincronización.

Este documento técnico propone añadir al estándar basic_osyncstream que envuelva un flujo de datos al que transferirá datos cuando se llame su destructor, de esta manera garantiza que los datos se envíen al flujo incluso en presencia de una excepción.

Ejemplo.

{
    std::osyncstream bout(std::cout); // Sincronismo sobre cout
    bout << "Hola," << '\n';          // No hay vaciado
    bout.emit();                      // Se transfiere el contenido actual a cout
    bout << "Mundo!" << std::endl;    // vaciado solicitado, pero no se envia a cout
    bout.emit();                      // Se transfiere el contenido a cout, hay flush
    bout << "Saludetes." << '\n';     // No hay vaciado
} // Se transfiere el contenido automáticamente a cout, no hay vaciado

Constantes matemáticas estandarizadas (documento técnico P0631R5).

Las constantes matemáticas como π y e son usadas frecuentemente en algoritmos pero no existe una definición de las mismas en ninguna cabecera estandarizada. Son muy fáciles de definir, pero es tan extraño como hacer una reserva en un restaurante en el que te piden que traigas tu propio salero.

Para suplir esta carencia, este documento técnico propone crear una colección de variables plantilla con los valores de las constantes más usadas, por citar algunas:

namespace math {
template<typename T > inline constexpr T e_v = ...
template<typename T > inline constexpr T log2e_v = ...
template<typename T > inline constexpr T log10e_v = ...
template<typename T > inline constexpr T pi_v = ...
...
}

Ejemplo.

Con tan sólo incluir la cabecera <math> podremos usar estas constantes:

#include <math>

int main() {
    std::cout << "Pi es " << std::math::pi<long double> << " exactamente\n";
    return 0;
}

Co-rutinas (documento técnico N3985).

Según la Wikipedia una co-rutina es:

En un programa, una corrutina es una unidad de tratamiento semejante a una subrutina, con la diferencia de que, mientras que la salida de una subrutina pone fin a esta, la salida de una corrutina puede ser el resultado de una suspensión de su tratamiento hasta que se le indique retomar su ejecución (multitarea cooperativa). La suspensión de la corrutina y su reanudación pueden ir acompañadas de una transmisión de datos.

Varios lenguajes de programación soportan las co-rutinas de manera nativa, este documento propone hacer las co-rutinas parte del estándar C++.

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82