Espacios de nombres
Variantes
Acciones

Expresiones constantes

De cppreference.com
< cpp‎ | language
 
 
 
 

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

Contenido

[editar] Expresiones constantes centrales

Una expresión constante central es cualquier expresión cuya evaluación no evaluaría ninguna de las siguientes:

  1. el puntero this, excepto en una función constexpr o constructor constexpr que se está evaluando como parte de la expresión;
  2. (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;
  3. 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
  4. una llamada a función a una función constexpr que está declarada, pero no definida;
  5. 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;
  6. (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;
  7. una expresión que excedería los límites definidos por la implementación;
  8. 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
  9. (hasta C++17) una expresión lambda;
  10. una conversión implícita de l-valor a r-valor, a menos que:
    1. 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
      }
    2. 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;
  11. 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);
  12. (desde C++20) una conversión implícita de l-valor a r-valor aplicada a un objeto con un valor indeterminado;
  13. 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.
  14. (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
  15. 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
  16. conversión desde const volatile void* a cualquier tipo puntero a objeto
  17. (hasta C++20)dynamic_cast
  18. reinterpret_cast
  19. (hasta C++20) llamada a un pseudodestructor
  20. (hasta C++14) un operador de incremento o decremento
  21. (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)
  22. (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
  23. (hasta C++20) una expresión typeid aplicada a un gl-valor de tipo polimórfico
  24. 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)
  25. 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)
  26. (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
  27. (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
  28. (desde C++20) una expresión await o una expresión yield
  29. (desde C++20) una comparación de tres vías cuando el resultado no está especificado
  30. un operador de igualdad o relacional cuando el resultado no está especificado
  31. (hasta C++14) un operador de asignación o de asignación compuesto
  32. una expresión throw
  33. (desde C++20) una declaración asm
  34. (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
  35. (desde C++23) una instrucción goto
  36. (desde C++20) una expresión dynamic_cast o typeid que lanzaría una excepción
  37. 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 ODR
    void 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>

    Nota: 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 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.
    (desde C++20)
    • si el valor es un objeto de clase o tipo array, cada subobjeto satisface estas restricciones para los valores
    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:

    (hasta C++14)

    [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

    Los siguientes contextos requieren una expresión constante convertida:

    • 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)
    (desde C++14)

    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
    • de tipo referencia o
    • de tipo entero calificado-const o de tipo enumeración
    • 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:
    (desde C++20)
    (desde C++17)
    (desde C++20)
    • 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 es constexpr

    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) [editar]
    Comprueba si un tipo es un tipo literal.
    (plantilla de clase) [editar]
    Documentación de C para Expresiones constantes