c++ - Diferencia entre `constexpr` y` const`




c++11 (5)

¿Cuál es la diferencia entre constexpr y const ?

  • ¿Cuándo puedo usar solo uno de ellos?
  • ¿Cuándo puedo usar ambos y cómo debo elegir uno?

Significado básico y sintaxis.

Ambas palabras clave se pueden utilizar en la declaración de objetos y funciones. La diferencia básica cuando se aplica a los objetos es la siguiente:

  • const declara un objeto como constante . Esto implica una garantía de que, una vez inicializado, el valor de ese objeto no cambiará, y el compilador puede hacer uso de este hecho para las optimizaciones. También ayuda a evitar que el programador escriba código que modifique los objetos que no debían modificarse después de la inicialización.

  • constexpr declara un objeto como apto para su uso en lo que el estándar llama expresiones constantes . Pero tenga en cuenta que constexpr no es la única manera de hacer esto.

Cuando se aplica a las funciones, la diferencia básica es la siguiente:

  • const solo se puede utilizar para funciones miembro no estáticas, no para funciones en general. Da una garantía de que la función miembro no modifica ninguno de los miembros de datos no estáticos.

  • constexpr se puede utilizar con funciones miembro y no miembro, así como con constructores. Declara la función apta para su uso en expresiones constantes . El compilador solo lo aceptará si la función cumple con ciertos criterios (7.1.5 / 3,4), lo más importante (†) :

    • El cuerpo de la función debe ser no virtual y extremadamente simple: aparte de typedefs y las afirmaciones estáticas, solo se permite una única declaración de return . En el caso de un constructor, solo se permiten una lista de inicialización, typedefs y static asert. (Sin embargo, también se permiten = delete = default y = delete ).
    • A partir de C ++ 14, las reglas están más relajadas, lo que está permitido desde entonces dentro de una función constexpr: asm declaración, una instrucción goto , una declaración con una etiqueta distinta de case y case , bloque de intentos, definición de una variable de non -tipo literal, definición de una variable de duración de almacenamiento de hilo o estática, definición de una variable para la que no se realiza la inicialización.
    • Los argumentos y el tipo de retorno deben ser tipos literales (es decir, en general, tipos muy simples, típicamente escalares o agregados)

Expresiones constantes

Como se dijo anteriormente, constexpr declara que tanto los objetos como las funciones son aptos para usar en expresiones constantes. Una expresión constante es más que meramente constante:

  • Se puede usar en lugares que requieren evaluación en tiempo de compilación, por ejemplo, parámetros de plantilla y especificadores de tamaño de matriz:

    template<int N>
    class fixed_size_list
    { /*...*/ };
    
    fixed_size_list<X> mylist;  // X must be an integer constant expression
    
    int numbers[X];  // X must be an integer constant expression
    
  • Pero tenga en cuenta:

    • Declarar algo como constexpr no garantiza necesariamente que se evaluará en el momento de la compilación. Puede usarse para eso, pero también puede usarse en otros lugares que se evalúan en tiempo de ejecución.

    • Un objeto puede ser adecuado para su uso en expresiones constantes sin ser declarado constexpr . Ejemplo:

      int main()
      {
        const int N = 3;
        int numbers[N] = {1, 2, 3};  // N is constant expression
      }
      

    Esto es posible porque N , al ser constante e inicializarse en el momento de la declaración con un literal, satisface los criterios para una expresión constante, incluso si no se declara constexpr .

Entonces, ¿cuándo tengo que usar constexpr ?

  • Un objeto como N arriba puede usarse como expresión constante sin ser declarado constexpr . Esto es cierto para todos los objetos que son:

    • const
    • de tipo integral o de enumeración y
    • inicializado en el momento de la declaración con una expresión que es en sí misma una expresión constante

    [Esto se debe a §5.19 / 2: una expresión constante no debe incluir subexpresiones que impliquen "una modificación de valor de rvalor a menos que [...] un valor de tipo integral o de enumeración [...]" Gracias a Richard Smith por corregir mi afirmación anterior de que esto era cierto para todos los tipos literales.]

  • Para que una función sea ​​apta para el uso en expresiones constantes, se debe declarar explícitamente constexpr ; no es suficiente para él simplemente satisfacer los criterios de las funciones de expresión constante. Ejemplo:

    template<int N>
    class list
    { };
    
    constexpr int sqr1(int arg)
    { return arg * arg; }
    
    int sqr2(int arg)
    { return arg * arg; }
    
    int main()
    {
      const int X = 2;
      list<sqr1(X)> mylist1;  // OK: sqr1 is constexpr
      list<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr
    }
    

¿Cuándo puedo / debo usar ambos, const y constexpr juntos?

A. En declaraciones de objeto. Esto nunca es necesario cuando ambas palabras clave hacen referencia al mismo objeto que se va a declarar. constexpr implica const .

constexpr const int N = 5;

es lo mismo que

constexpr int N = 5;

Sin embargo, tenga en cuenta que puede haber situaciones en las que las palabras clave se refieran a diferentes partes de la declaración:

static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

Aquí, NP se declara como una expresión constante de dirección, es decir, un puntero que es en sí mismo una expresión constante. (Esto es posible cuando la dirección se genera aplicando el operador de dirección a una expresión constante estática / global). Aquí se requieren tanto constexpr como const : constexpr siempre se refiere a la expresión que se está declarando (aquí NP ), mientras que const refiere a int (Se declara un puntero a const). Eliminar la const haría que la expresión fuera ilegal (porque (a) un puntero a un objeto no constante no puede ser una expresión constante, y (b) &N es en realidad un puntero a constante).

B. En declaraciones de función miembro. En C ++ 11, constexpr implica const , mientras que en C ++ 14 y C ++ 17 ese no es el caso. Una función miembro declarada bajo C ++ 11 como

constexpr void f();

necesita ser declarado como

constexpr void f() const;

bajo C ++ 14 para seguir utilizándose como una función const .


Visión general

  • const garantiza que un programa no cambia el valor de un objeto . Sin embargo, const no garantiza qué tipo de inicialización sufre el objeto.

    Considerar:

    const int mx = numeric_limits<int>::max();  // OK: runtime initialization
    

    La función max() simplemente devuelve un valor literal. Sin embargo, debido a que el inicializador es una llamada de función, mx sufre una inicialización en tiempo de ejecución. Por lo tanto, no puedes usarlo como una expresión constante :

    int arr[mx];  // error: “constant expression required”
    
  • constexpr es una nueva palabra clave de C ++ 11 que elimina la necesidad de crear macros y literales codificados. También garantiza, bajo ciertas condiciones, que los objetos experimentan una inicialización estática . Controla el tiempo de evaluación de una expresión. Al imponer la evaluación en tiempo de compilación de su expresión , constexpr permite definir verdaderas expresiones constantes que son cruciales para las aplicaciones de tiempo crítico, la programación del sistema, las plantillas y, en general, en cualquier código que se basa en constantes de tiempo de compilación.

Funciones de expresión constante

Una función de expresión constante es una función declarada constexpr . Su cuerpo debe ser no virtual y constar de una sola declaración de retorno, aparte de typedefs y aserciones estáticas. Sus argumentos y valor de retorno deben tener tipos literales. Se puede usar con argumentos de expresiones no constantes, pero cuando se hace eso, el resultado no es una expresión constante.

Una función de expresión constante está destinada a reemplazar macros y literales codificados sin sacrificar el rendimiento o la seguridad de tipos.

constexpr int max() { return INT_MAX; }           // OK
constexpr long long_max() { return 2147483647; }  // OK
constexpr bool get_val()
{
    bool res = false;
    return res;
}  // error: body is not just a return statement

constexpr int square(int x)
{ return x * x; }  // OK: compile-time evaluation only if x is a constant expression
const int res = square(5);  // OK: compile-time evaluation of square(5)
int y = getval();
int n = square(y);          // OK: runtime evaluation of square(y)

Objetos de expresión constante

Un objeto de expresión constante es un objeto declarado constexpr . Se debe inicializar con una expresión constante o un valor r construido por un constructor de expresión constante con argumentos de expresión constante.

Un objeto de expresión constante se comporta como si hubiera sido declarado const , excepto que requiere inicialización antes de su uso y su inicializador debe ser una expresión constante. En consecuencia, un objeto de expresión constante siempre se puede utilizar como parte de otra expresión constante.

struct S
{
    constexpr int two();      // constant-expression function
private:
    static constexpr int sz;  // constant-expression object
};
constexpr int S::sz = 256;
enum DataPacket
{
    Small = S::two(),  // error: S::two() called before it was defined
    Big = 1024
};
constexpr int S::two() { return sz*2; }
constexpr S s;
int arr[s.two()];  // OK: s.two() called after its definition

Constructores de expresiones constantes

Un constructor de expresión constante es un constructor declarado constexpr . Puede tener una lista de inicialización de miembros, pero su cuerpo debe estar vacío, aparte de typedefs y afirmaciones estáticas. Sus argumentos deben tener tipos literales.

Un constructor de expresiones constantes permite al compilador inicializar el objeto en tiempo de compilación, siempre que los argumentos del constructor sean todas expresiones constantes.

struct complex
{
    // constant-expression constructor
    constexpr complex(double r, double i) : re(r), im(i) { }  // OK: empty body
    // constant-expression functions
    constexpr double real() { return re; }
    constexpr double imag() { return im; }
private:
    double re;
    double im;
};
constexpr complex COMP(0.0, 1.0);         // creates a literal complex
double x = 1.0;
constexpr complex cx1(x, 0);              // error: x is not a constant expression
const complex cx2(x, 1);                  // OK: runtime initialization
constexpr double xx = COMP.real();        // OK: compile-time initialization
constexpr double imaglval = COMP.imag();  // OK: compile-time initialization
complex cx3(2, 4.6);                      // OK: runtime initialization

Consejos del libro Effective Modern C ++ de Scott Meyers sobre constexpr :

  • constexpr objetos constexpr son const y se inicializan con valores conocidos durante la compilación;
  • constexpr funciones constexpr producen resultados en tiempo de compilación cuando se llaman con argumentos cuyos valores se conocen durante la compilación;
  • constexpr objetos y funciones constexpr pueden utilizarse en una gama más amplia de contextos que los objetos y funciones no constexpr ;
  • constexpr es parte de la interfaz de un objeto o función.

Fuente: uso de constexpr para mejorar la seguridad, el rendimiento y la encapsulación en C ++ .


Como @ 0x499602d2 ya señaló, const solo garantiza que no se pueda cambiar un valor después de la inicialización, mientras que constexpr (introducido en C ++ 11) garantiza que la variable es una constante de tiempo de compilación.
Considere el siguiente ejemplo (de LearnCpp.com):

cout << "Enter your age: ";
int age;
cin >> age;

const int myAge{age};        // works
constexpr int someAge{age};  // error: age can only be resolved at runtime

De acuerdo con el libro de "The C ++ Programming Language 4th Editon" de Bjarne Stroustrup
const : que significa aproximadamente "Prometo no cambiar este valor" (§7.5). Esto se usa principalmente para especificar interfaces, de modo que los datos se pueden pasar a las funciones sin temor a que se modifiquen.
El compilador hace cumplir la promesa hecha por const.
constexpr : significa aproximadamente "para ser evaluado en tiempo de compilación" (§10.4). Esto se usa principalmente para especificar constantes, para permitir
Por ejemplo:

const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression

Para que una función sea utilizable en una expresión constante, es decir, en una expresión que será evaluada por el compilador, debe definirse constexpr .
Por ejemplo:

constexpr double square(double x) { return x∗x; }


Para ser constexpr, una función debe ser bastante simple: solo una declaración de retorno que calcula un valor. Se puede usar una función constexpr para argumentos no constantes, pero cuando se hace eso, el resultado no es una expresión constante. Permitimos que se llame a una función constexpr con argumentos de expresiones no constantes en contextos que no requieren expresiones constantes, por lo que no tenemos que definir esencialmente la misma función dos veces: una para expresiones constantes y una para variables.
En algunos lugares, las reglas de idioma requieren expresiones constantes (por ejemplo, límites de matriz (§2.2.5, §7.3), etiquetas de caso (§2.2.4, §9.4.2), algunos argumentos de plantilla (§25.2) y constantes declaradas utilizando constexpr). En otros casos, la evaluación en tiempo de compilación es importante para el desempeño. Independientemente de los problemas de rendimiento, la noción de inmutabilidad (de un objeto con un estado inmutable) es una preocupación importante del diseño (§10.4).


const aplica a las variables y evita que se modifiquen en su código.

constexpr le dice al compilador que esta expresión da como resultado un valor constante de tiempo de compilación , por lo que puede usarse en lugares como longitudes de matriz, asignando variables const , etc. El link dado por Oli tiene muchos ejemplos excelentes.

Básicamente, son 2 conceptos diferentes en conjunto, y pueden (y deben) usarse juntos.







constexpr