move_only_function
(antes conocida como any_invocable
).
Se propone una versión de std::function
que:
- No sea copialbe (sólo movible).
- Resuelva el bug de la correctitud constante de
std::function
detallado en n4348.
- De soporte a los cualificadores de función
const
, volatile
, referencia y noexcept
.
- Carezca de
target_type
y target
.
- Su invocación tenga estrictas precondiciones.
Añadir std::hive
(colmena) a la librería estándar.
El objetivo de un contenedor en la librería estándar no puede ser proveer de la solución óptima en todos los escenarios. Existirán campos en que la solución óptima sea un contenedor propio que se ajuste a un escenario concreto. Aún así existen soluciones para casos generalizados que aún no forman parte de la librería estándar.
Una colmena (hive) es la formalización, extensión y optimización de lo que típicamente se conoce como 'bucket array' cuyo concepto es:
Se dispone de múltiples bloques de memoria y una bandera para cada elemento que denota si el elemento está activo o borrado. Si un elemento está borrado es ignorado durante las iteraciones, cuando todos los elementos de un bloque están marcados como borrados dicho bloque se elimina. Si se inserta un nuevo elemento cuando todos los bloques están llenos, se crea un nuevo bloque de memoria.
Marcar código inalcanzable.
Los compiladores no pueden conocer todas las situaciones en que un código puede ejecutarse, siempre existirán programas en que el compilador no pueda determinar que una situación es imposible, por ejemplo:
void f(int numero_que_solo_es_0_1_2_o_3)
{
switch (numero_que_solo_es_0_1_2_o_3)
{
case 0:
case 2:
f02();
break;
case 1:
f1();
break;
case 3:
f3();
break;
}
}
Ese código puede producir el siguiente código máquina...
cmp eax, 4
jae skip_switch
lea rcx, [jump_table]
jmp qword [rcx + rax*8]
... del que las dos primeras instrucciones son innecesarias. Este tipo de problemas surgen cuando el programador sabe que una situación es imposible pero no es obvio para el compilador; sería útil poder decirle al compilador que evite comprobaciones en tiempo de ejecución para casos que sabemos que son imposibles. Se propone para ello crear una función en la librería estándar:
namespace std {
…
[[noreturn]] void unreachable();
…
}
Dejando el ejemplo inicial así:
void f(int numero_que_solo_es_0_1_2_o_3)
{
switch (numero_que_solo_es_0_1_2_o_3)
{
case 0:
case 2:
f02();
break;
case 1:
f1();
break;
case 3:
f3();
break;
default:
std::unreachable();
}
}
auto(x)
.
Se propone auto(x)
y auto{x}
para convertir x
en un prvalue (valor del lado derecho puro) como si se hubiera pasado x
como argumento a una función por valor. Uno de los casos de uso se ilustra pasando el siguiente código:
void pop_front_alike(Container auto& x) {
auto a = x.front();
std::erase(x.begin(), x.end(), a);
}
A este código:
void pop_front_alike(Container auto& x) {
std::erase(x.begin(), x.end(), auto(x.front()));
}
En el segundo caso queda explicitado que se quiere pasar una copia anónima. Sería una estandarización en el lenguaje de algo que a día de hoy es engorroso de usar:
template<class T>
constexpr decay_t<T> decay_copy(T&& v) noexcept(
is_nothrow_convertible_v<T, decay_t<T>>) {
return std::forward<T>(v);
}
Operador de indizado multidimensional.
Se propone que el operador de indizado (operator []
) pueda aceptar entre cero o múltiples argumentos (incluidos argumentos variádicos). No se propone cambiar el funcionamiento de las formaciones:
struct S {
S operator[](auto ...);
} s;
s[1, 2, 3] = 7; // Correcto, llama a s.operator[](1, 2, 3)
int a[10];
a[1, 2, 3] = 7; // Error, no se permiten expresiones con coma en el indizado de formaciones
Operador paréntesis estático static operator()
.
Se propone permitir que el operador de llamada (también conocido como operador paréntesis operator()
) sea estático en lugar de requerir que sea una función miembro no estática. Esto permitirá evitar el problema de pasar un this
implícito cuando se usan objetos función sin miembros (como std::less
).
Cambiar el ámbito del tipo del retorno arrastrado de las lambdas.
La manera en que funciona la búsqueda de símbolos en las lambdas es sorprendente: funciona de manera diferente en el cuerpo de la lambda y en el tipo del retorno arrastrado. Por ejemplo este código no compila:
auto lambda = [j = 0]() mutable -> decltype(j) {
return j++;
};
Esto sucede porque la variable j
que se declara en la captura no es visible en el momento de preguntar por su tipo: la j
de la captura forma parte del cuerpo de la lambda mientras que la j
del tipo de retorno arrastrado se busca fuera, provocando el error de compilación, pero podría ser peor:
double j = 42.0;
// ...
// Varios centenares de líneas de código
// ...
auto lambda = [j = 0]() mutable -> decltype(j) {
return j++;
};
El código anterior sí que compila pero hace que el retorno de la lambda sea double
cuando probablemente la intención era que fuese int
. Para evitar estos problemas se propone que la búsqueda de símbolos empiece la búsqueda en el retorno arrastrado de la lambda en lugar de buscar directamente en el ámbito externo a la lambda.
basic_string::resize_and_overwrite
.
La clase basic_string
tiene ciertos problemas de rendimiento al inicializar o manipular; para esas tareas se debe tomar una difícil decisión:
- Inicializar espacio de más:
resize
inicializa a cero los datos y después los copia.
- Hacer copias de más: Rellenar un búfer temporal que será copiado a la cadena.
- Reservar por si acaso:
reserve
seguido de append
, en cada operación de anexado se verifica la capacidad y de basic_string
.
Para solucionar este problema se propone una nueva función miembro para la clase basic_string
:
template<class Operation>
constexpr void resize_and_overwrite(size_type n, Operation op);
Cuyo comportamiento sería:
- Si
n <= size()
, borra los últimos size() - n elements
.
- Si
n > size()
, anexa n - size()
elementos inicializados por defecto.
- Llama
erase(begin() + op(data(), n), end())
.
Arreglar el bucle for
de rango.
En C++ es posible crear referencias colgantes (dangling references) por descuido:
// Funciones que devuelven temporales creados al vuelo
std::tuple<std::string, int, int> dame_tupla() { return {}; }
std::vector<std::string> dame_textos() { return {}; }
int main()
{
// Referencias colgantes
const auto &str1 = std::get<0>(dame_tupla()); // Referencia a sub-elemento de temporal.
const std::string &str2 = dame_textos()[0]; // Referencia a sub-elemento de temporal.
std::cout << str1 << ' ' << str2 << '\n';
return 0;
}
Al menos estas referencias colgantes son visibles en el código, pero si formasen parte de un bucle for
de rango, quedarían ocultas:
for (auto elemento : get<0>(dame_tupla()))
...
for (auto elemento : dame_textos()[0])
...
Se propone modificar la implementación interna del bucle for
de rango para esquivar este problema, pasando de su implementación actual:
{
init-statement
auto && __range = range-expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range-declaration = *__begin;
loop-statement
}
}
A esta:
{
init-statementopt
auto && range = for-range-initializer;
[&](auto&& range) {
auto begin = begin-expr ;
auto end = end-expr ;
for ( ; begin != end ; ++begin ) {
for-range-declaration = * begin ;
statement // return o goto o instrucciones co- afectan a todo el bucle
}
}( for-range-initializer );
}
Salida con formato.
La función std::format
de C++20 tiene problemas de rendimiento:
std::cout << std::format("Hola, {}!", nombre);
El código anterior crea una cadena temporal que requiere de su constructor y destructor para después pasar dicha cadena al operador de escritura con formato de std::cout
cuando dicha cadena ya había sido formateada. Se propone crear una función que de formato al texto directamente en la salida:
std::print("Hola, {}!", nombre);
Arreglar cbegin
.
Las funciones cbegin()
y cend()
permiten acceder a una colección de datos con iteradores constantes, esto permite evitar que se puedan modificar los elementos iterados. Antes de C++20 este comportamiento estaba garantizado ya que las colecciones de datos propagaban la propiedad de sólo lectura a sus elementos (si la colección es de sólo lectura, también lo serán sus elementos). Pero desde C++20 se han proveído contenedores/rangos que no propagan la propiedad de sólo lectura.
Para tipos como std::span
o vistas, se pueden modificar los elementos incluso aunque el contenedor/rango se declare como constante. Para esos tipos el código genérico std::cbegin()
, std::cend()
y similares está roto: siempre llama a los miembros begin()
y end()
(aunque los miembros cbegin()
y cend()
existan) por lo que es posible modificar elementos aún iterando con cbegin()
y cend()
.
Idealmente esto se debería solucionar, sin embargo otras propuestas opinan lo contrario: P2276R0 y P2278R0, por ello se proponen ciertos cambios para minimizar las consecuencias de este comportamiento:
- Proveer los miembros
cbegin()
y cend()
para std::span
.
- Proveer un mecanismo (conceptos) para verificar si un contenedor propaga o no la propiedad de sólo lectura.
- Deshabilitar
std::cbegin()
, std::cend()
, etc... cuando una colección de datos no propague la propiedad de sólo lectura.
- Deshabilitar
std::ranges::cbegin()
, std::ranges::cend()
, etc... cuando el rango no propague la propiedad de sólo lectura.
ranges::fold
.
Se propone crear un algoritmo fold en la cabecera <algorithm>
ya que la librería estándar carece del dicho algoritmo sobre rangos. Aunque existe una versión de fold basada en iteradores, actualmente recibe el nombre de std::accumulate
, por defecto suma elementos y se aloja en la cabecera <numeric>
. Pero fold es mucho más que sumar, así que es importante darle un nombre más genérico y evitar que la operación por defecto.
Reflexión escalable.
Se propone dotar al lenguaje de mecanismos de reflexión estática, la propuesta incluye la creación de un nuevo operador unario (^
):
constexpr std::meta::info refl = ^nombre_o_expresion;
Dando como resultado un valor conocido en tiempo de compilación que identifica el elemento de manera dependiente de implementación. El operador de reflexión se puede usar sobre:
- Un nombre de tipo.
- Un nombre de plantilla.
- Un espacio de nombres.
- El espacio de nombres raíz
::
.
- Una expresión.
Se propone que el tipo std::meta::info
sea un nuevo tipo de escalar que sólo soporte conversión a bool
y equidad/inequidad. Se usará como parámetro para diferentes funciones de reflexión como por ejemplo:
consteval bool is_namespace(info entity) {...};
consteval bool is_template(info entity) {...};
consteval bool is_type(info entity) {...};
Pero se puede usar también directamente:
typedef int I1;
typedef int I2;
static_assert(^I1 == ^I2); // Correcto
static_assert(^I1 == ^int); // Correcto