Expresiones constantes
Define una expresión que puede evaluarse en tiempo de compilación.
Dichas expresiones se pueden usar como argumentos de plantilla sin tipo, tamaños de array y en otros contextos que requieren expresiones constantes, por ejemplo:
int n = 1; std::array<int, n> a1; // ERROR: n no es una expresión constante const int cn = 2; std::array<int, cn> a2; // de acuerdo: cn es una expresión constante
[editar] Expresiones constantes centrales
Una expresión constante central es cualquier expresión cuya evaluación no evaluaría ninguna de las siguientes:
- el puntero
this
, excepto en una funciónconstexpr
o constructorconstexpr
que se está evaluando como parte de la expresión; - (desde C++23) un flujo de control que pasa a través de una declaración de una variable con una duración de almacenamiento estática o de hilo;
- una expresión de llamada a función que llama a una función (o constructor) que no está declarado constexpr;
constexpr int n = std::numeric_limits<int>::max(); // de acuerdo: max() es constexpr constexpr int m = std::time(nullptr); // ERROR: std::time() no es constexpr
- una llamada a función a una función
constexpr
que está declarada, pero no definida; - una llamada a función a instancia de plantilla de función/constructor
constexpr
donde la instanciación falla al satisfacer los requerimientos de una función o constructor constexpr; - (desde C++20) una llamada a función a una función
constexpr virtual
, invocada sobre un objeto no utilizable en expresiones constantes y cuyo tiempo de vida empezó fuera de esta expresión; - una expresión que excedería los límites definidos por la implementación;
- una expresión cuya evaluación conduce a cualquier forma de comportamiento no definido del lenguaje principal (incluyendo desbordamiento de enteros con signo, división por cero, aritmética de punteros fuera de los límites de un array, etc). No se especifica si se detecta un comportamiento no definido de la biblioteca estándar.
constexpr double d1 = 2.0/1.0; // de acuerdo constexpr double d2 = 2.0/0.0; // ERROR: sin definir constexpr int n = std::numeric_limits<int>::max() + 1; // ERROR: desbordamiento int x, y, z[30]; constexpr auto e1 = &y - &x; // ERROR: sin definir constexpr auto e2 = &z[20] - &z[3]; // de acuerdo constexpr std::bitset<2> a; constexpr bool b = a[2]; // comportamiento no definido, pero no se especifica si se detecta
- (hasta C++17) una expresión lambda;
- una conversión implícita de l-valor a r-valor, a menos que:
- se aplique a un gl-valor no volátil que designe a un objeto que es utilizable en expresiones constantes;
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // de acuerdo: tabsize es una expresión constante // porque tabsize es utilizable en expresiones constantes // ya que tiene tipo entero calificado const, // y su inicializador es un inicializador constante std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // ERROR: sz no es una expresión constante // porque sz no es utilizable en expresiones constantes // ya que su inicializador no es un inicializador constante }
- o se aplique a un tipo literal gl-valor no volátil que se refiere a un objeto no volátil cuyo tiempo de vida empezó con la evaluación de esta expresión;
- se aplique a un gl-valor no volátil que designe a un objeto que es utilizable en expresiones constantes;
- una conversión implícita de l-valor a r-valor o modificación aplicada a un miembro no activo de una union o su subobjeto (incluso si comparte una secuencia inicial común con el miembro activo);
- (desde C++20) una conversión implícita de l-valor a r-valor aplicada a un objeto con un valor indeterminado;
- una invocación de un constructor de copia/movimiento definido implícitamente, u operador de asignación de copia/movimiento para una unión cuyo miembro activo (si es que lo tiene) es mutable, a menos que el tiempo de vida del objeto haya empezado dentro de la evaluación de esta expresión.
- (desde C++17) (hasta C++20) una expresión de asignación o la invocación de un operador de asignación sobrecargado que podría cambiar el miembro activo de una unión
- una expresión id que se refiere a una variable o dato miembro de tipo referencia, a menos que la referencia sea utilizable en expresiones constantes o su tiempo de vida comience dentro de la evaluación de esta expresión
- conversión desde
const volatile
void* a cualquier tipo puntero a objeto - (hasta C++20)
dynamic_cast
-
reinterpret_cast
- (hasta C++20) llamada a un pseudodestructor
- (hasta C++14) un operador de incremento o decremento
-
(desde C++14) la modificación de un objeto, a menos que el objeto tenga tipo literal no volátil y su tiempo de vida comience dentro de la evaluación de la expresión
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // ERROR: incr(k) no es una expresión constante // central porque el tiempo de vida de k // comienza fuera de la expresión incr(k) return x; } constexpr int h(int k) { int x = incr(k); // de acuerdo: no se requiere que x se inicie con // una expresión constante central return x; } constexpr int y = h(1); // de acuerdo: y se inicia con el valor 2 // h(1) es una expresión constante central porque // el tiempo de vida de k comienza dentro de la expresión h(1)
- (desde C++20) una llamada a destructor o pseudodestructor para un objeto cuyo tiempo de vida empezó dentro de la evaluación de esta expresión
- (hasta C++20) una expresión
typeid
aplicada a un gl-valor de tipo polimórfico - una expresión new, a menos que la función de asignación de memoria seleccionada sea una función de asignación global reemplazable y el almacenamiento asignado se desasigne dentro de la evaluación de esta expresión (desde C++20)
- una expresión delete, a menos que desasigne una región de almacenamiento asignada dentro de la evaluación de esta expresión (desde C++20)
- (desde C++20) una llamada a std::allocator<T>::allocate, a menos que el almacenamiento asignado se desasigne dentro de la evaluación de esta expresión
- (desde C++20) una llamada a std::allocator<T>::deallocate, a menos que desasigne una región de almacenamiento asignado dentro de la evaluación de esta expresión
- (desde C++20) una expresión await o una expresión yield
- (desde C++20) una comparación de tres vías cuando el resultado no está especificado
- un operador de igualdad o relacional cuando el resultado no está especificado
- (hasta C++14) un operador de asignación o de asignación compuesto
- una expresión
throw
- (desde C++20) una declaración asm
- (desde C++14) una invocación a la macro va_arg macro, no se especifica si la invocación de la macro va_start pueda evaluarse
- (desde C++23) una instrucción
goto
- (desde C++20) una expresión
dynamic_cast
otypeid
que lanzaría una excepción - dentro de una expresión lambda, una referencia a
this
o a una variable definida fuera de esta lambda, si esa referencia fuera un uso ODRvoid g() { const int n=0; constexpr int j=*&n; // de acuerdo: fuera de una expresión lambda [=]{ constexpr int i=n; // de acuerdo: 'n' no es de uso ODR y no se captura aquí constexpr int j=*&n;// mal formado: '&n' sería un uso ODR de 'n'. }; }
ten en cuenta que si el uso ODR toma lugar en una llamada a función a un cierre (closure), no se refiere a
this
o a una variable circundante, ya que en su lugar accede al dato miembro del cierre.// de acuerdo: 'v' y 'm' son de uso ODR pero no ocurren en una expresión contante // dentro de la lambda anidada auto monad = [](auto v){return [=]{return v;};}; auto bind = [](auto m){return [=](auto fvm){return fvm(m());};}; // está bien tener capturas de objetos automáticos creados durante la evaluación de la expresión constante. static_assert(bind(monad(2))(monad)() == monad(2)());
(desde C++17) </ol>
Esta sección está incompleta
Razón: se necesitan más miniejemplos y menos lenguaje del estándarNota: el simple hecho de ser una expresión constante central no tiene ningún significado semántico directo: una expresión debe ser uno de los siguientes subconjuntos (véase abajo) para ser usada en ciertos contextos.
[editar] Expresión constante
Una expresión constante es ya sea
- una expresión constante central l-valor (hasta C++14)gl-valor (desde C++14) que se refiere a:
- un objeto con duración de almacenamiento estática que no es un temporal, o
- un objeto con una duración de almacenamiento estática que es un temporal, pero cuyo valor satisface las restricciones para los pr-valores a continuación, o
(desde C++14) - una función no-inmediata (desde C++20)
- una expresión constante central pr-valor cuyo valor satisface las restricciones siguientes:
- si el valor es un objeto de tipo clase, cada dato miembro no estático de tipo referencia se refiere a una entidad que satisface las restricciones para los l-valores (hasta C++14)gl-valores (desde C++14) anteriores
- si el valor es de tipo puntero, alberga
- la dirección de un objeto con duración de almacenamiento estática;
- la dirección más allá del final de un objeto con duración de almacenamiento estática;
- la dirección de una función no-inmediata (desde C++20);
- un valor de puntero nulo.
- si el valor de tipo puntero a función miembro, no designa una función inmediata
(desde C++20) - si el valor es un objeto de clase o tipo array, cada subobjeto satisface estas restricciones para los valores
Esta sección está incompleta
Razón: ¿Cuál es la lista de contextos que requieren expresiones constantes que no son enteras/convertidas?void test() { static const int a = std::random_device{}(); constexpr const int& ra = a; // de acuerdo: a es una expresión constante glvalue constexpr int ia = a; // ERROR: a no es una expresión constante prvalue const int b = 42; constexpr const int& rb = b; // ERROR: b no es una expresión constante glvalue constexpr int ib = b; // de acuerdo: b es una expresión constante prvalue }
[editar] Expresión constante entera
Una expresión constante entera es una expresión de tipo entero o tipo enumeración sin ámbito convertida implícitamente a un pr-valor, donde la expresión convertida es una expresión constante central. Si se usa una expresión de tipo clase donde se espera una expresión constante entera, la expresión se convierte implícitamente según el contexto a un tipo entero o tipo enumeración sin ámbito.
Los siguientes contextos requieren una expresión constante entera:
- límites de arrays;
- las dimensiones en las expresiones new distintas de la primera;
(hasta C++14) - longitudes de campos de bits
- los inicializadores de enumeraciones cuando el tipo subyacente no está fijo;
- alineaciones.
[editar] Expresión constante convertida
Una expresión constante convertida de tipo
T
es una expresión convertida implícitamente al tipo T, donde la expresión convertida es una expresión constante, y la secuencia de conversión implícita solamente contiene:- conversiones
constexpr
definidas por el usuario (por lo que se puede usar una clase donde se espera un tipo entero) - conversiones de l-valor a r-valor
- promociones enteras
- conversiones enteras no estrechantes
- conversiones
- y si toma lugar alguna vinculación de referencia, se vincula directamente (no una que construya un objeto temporal)
Los siguientes contextos requieren una expresión constante convertida:
- expresiones case;
- inicializadores de enumeradores donde el tipo subyacente es fijo;
- conversiones de array a puntero
- conversiones de función a puntero
- conversiones de puntero a función (de puntero a función
noexcept
a puntero a función) - conversiones de calificación
- conversiones de puntero nulo a partir de std::nullptr_t
- conversiones de puntero a miembro nulo a partir de std::nullptr_t
(desde C++17) - límites de arrays;
- las dimensiones en las expresiones new excepto la primera;
(desde C++14) - argumentos de plantilla enteros y enumeraciones (hasta C++17) sin tipo.
Una expresión constante convertida según el contexto de tipo bool es una expresión, convertida contextualmente a bool, donde la expresión convertida es una expresión constante y la secuencia de conversión contiene solamente las conversiones anteriores.
Los siguientes contextos requieren una expresión constante contextualmente convertida de tipo bool:
(hasta C++23) (desde C++17)
(hasta C++23)(desde C++20) [editar] Categorías históricas
Las categorías de las expresiones constantes listadas abajo ya no se usan en el estándar desde C++14:
- Una expresión constante de literal es una expresión constante central pr-valor de tipo literal no-puntero (después de las conversiones requeridas por el contexto). Una expresión constante de literal de tipo array tipo clase requiere que cada subobjeto se inicialice con una expresión constante.
- Una expresión constante de referencia es una expresión constante central l-valor que designa un objeto con duración de almacenamiento estática o una función.
- Una expresión constante de dirección es una expresión constante central pr-valor (después de las conversiones requeridas por el contexto) de tipo std::nullptr_t o de un tipo puntero, que apunta a un objeto con duración de almacenamiento estática, a un elemento después del final de un array con duración de almacenamiento estática, o a una función, o es el puntero nulo.
[editar] Utilizable en expresiones constantes
En la lista anterior, una variable es utilizable en expresiones constantes en un punto P si
- la variable es
- una variable
constexpr
, o - es una variable inicializada por constante
- de tipo referencia o
- de tipo entero calificado-
const
o de tipo enumeración
- una variable
- y la definición de la variable puede alcanzarse desde P
- y, si P se encuentra en la misma unidad de traducción que la definición de la variable (es decir, la definición es importada), la variable no se inicializa para que apunte a, se refiera a o tenga un subobjeto (posiblemente recursivo) que apunte a o se refiera a, una entidad local de unidad de traducción que sea utilizable en expresiones constantes
(desde C++20) Un objeto o referencia es utilizable en expresiones constantes si es
- una variable que es utilizable en expresiones constantes, o
- (desde C++20) un objeto de parámetro de plantilla, o
- un objeto de literal de cadena, o
- un subobjeto no mutable o referencia miembro de cualquiera de los anteriores, o
- un objeto temporal completo de tipo entero no volátil, calificado
const
, o tipo enumeración que se inicializa con una expresión constante.
const std::size sz = 10; // sz is utilizable en expresiones constantes
[editar] Expresiones manifestadamente evaluadas constante
Las siguientes expresiones (incluyendo conversiones al tipo destino) son manifestadamente evaluadas constante:
- Donde se requiera gramaticalmente una expresión constante, incluyendo:
- límites de arrays;
- las dimensones en las expresiones new excepto la primera;
- longitudes de campos de bits;
- inicializadores de enumeración;
- alineaciones;
- expresiones
case
; - argumentos de plantilla sin tipo;
- expresiones en especificaciones noexcept;
- expresiones en declaraciones
static_assert
;
- expresiones en especificadores
explicit
condicionales;
- expresiones en especificadores
(desde C++20) - condiciones de instrucciones constexpr if;
(desde C++17) - invocaciones inmediatas;
- expresiones de restricción en definiciones de conceptos, requerimientos anidados, y cláusulas requiere;
(desde C++20) - inicializadores de variables que son utilizables en expresiones constantes, incluyendo:
- inicializadores de variables constexpr;
- inicializadores de variables con tipo referencia o enteras calificadas-const, o tipo enumeración, cuando los inicializadores son expresiones constantes;
- inicializadores de variables estáticas (static) y locales al hilo (thread_local), cuando todas las subexpresiones de los inicializadores (incluyendo llamadas al constructor y conversiones implícitas) son expresiones constantes (es decir, cuando los inicializadores son inicializadores constantes).
Ten en cuenta que el contexto de los últimos dos casos también acepta expresiones no constantes.
Si ocurre o no una evaluación en un contexto manifestadamente evaluado constante puede detectarse mediante std::is_constant_evaluated y
if consteval
(desde C++23).Para probar las últimas dos condiciones, los compiladores pueden primero realizar una prueba de evaluación constante de los inicializadores. En este caso no se recomienda depender del resultado.
(desde C++20) [editar] Funciones y variables necesarias para la evaluación constante
Las siguientes expresiones o conversiones son potencialmente evaluadas constante:
- expresiones manifestadamente evaluadas constante;
- expresiones potencialmente evaluadas;
- subexpresiones inmediatas de una inicialización por lista entre llaves (la evaluación constante puede ser necesaria para determinar si una conversión es estrechante);
- expresiones dirección-de (
&
unario) que ocurren dentro de una entidad emplantillada (la evaluación constante puede ser necesaria para determinar si tal expresión es dependiente del valor) - subexpresiones de una de las definidas anteriormente que no sean una subexpresión de un operando no evaluado anidado;
Una función es necesaria para la evaluación constante si es una función constexpry denominada por una expresión que es potencialmente evaluada constante.
Una variable es necesaria para la evaluación constante si es o bien una variable constexpr o es un tipo entero no-volatile calificado-const o de tipo referencia y la expresión-id que la denota es potencialmente evaluada constante.
La definición de una función marcada con
= default
y la instanciación de una especialización de plantilla de función o especialización de plantilla de variable (desde C++14) se disparan si la función o variable (desde C++14) es necesaria para la evaluación constante.[editar] Notas
Las implementaciones no tienen permitido declarar las funciones de biblioteca como
constexpr
, a no ser que el estándar diga que la función esconstexpr
La optimización del valor de retorno nombrado (NRVO por sus siglas en inglés) no se permite en expresiones constantes, mientras que la optimización del valor de retorno (RVO por sus siglas en inglés) es obligatoria.
[editar] Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID Aplicado a Comportamiento según lo publicado Comportamiento correcto CWG 1313 C++11 se permitía comportamiento no definido, y todas
las restas de punteros estaban prohibidas
se permite la resta de punteros mismo array,
se prohíbe el comportamiento no definido
CWG 1952 C++11 se requirió un comportamiento no definido de la
biblioteca estándar para ser diagnosticado
no se especifica si el comportamiento no definido
de la biblioteca se diagnostica
CWG 2167 C++14 referencias a no miembros locales a una evaluación
hacían la evaluación no constexpr
se permiten las referencias a no miembros CWG 2299 C++11 no estaba claro si se podían usar macros en <cstdarg>
en la evaluación de constante
va_arg
prohibido,va_start
sin especificar[editar] Véase también
Especificador constexpr Especifica que el valor de una variable o función se puede calcular en tiempo de compilación (C++11) (C++11)Comprueba si un tipo es un tipo literal.
(plantilla de clase)Documentación de C para Expresiones constantes