c++ - Diferencia entre `constexpr` y` const`
c++11 (7)
¿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 queconstexpr
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óngoto
, una declaración con una etiqueta distinta decase
ycase
, 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)
- 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
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 declaraconstexpr
.
Entonces, ¿cuándo tengo que usar constexpr
?
Un objeto como
N
arriba puede usarse como expresión constante sin ser declaradoconstexpr
. 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
objetosconstexpr
son const y se inicializan con valores conocidos durante la compilación; -
constexpr
funcionesconstexpr
producen resultados en tiempo de compilación cuando se llaman con argumentos cuyos valores se conocen durante la compilación; -
constexpr
objetos y funcionesconstexpr
pueden utilizarse en una gama más amplia de contextos que los objetos y funciones noconstexpr
; -
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 ++ .
A const int var
se puede establecer dinámicamente en un valor en tiempo de ejecución y una vez que se establece en ese valor, ya no se puede cambiar.
A constexpr int var
no se puede establecer dinámicamente en tiempo de ejecución, sino más bien, en tiempo de compilación. Y una vez que se establece en ese valor, ya no se puede cambiar.
Aquí hay un ejemplo sólido:
int main(int argc, char*argv[]) {
const int p = argc;
// p = 69; // cannot change p because it is a const
// constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time
constexpr int r = 2^3; // this works!
// r = 42; // same as const too, it cannot be changed
}
El fragmento de código de arriba compila bien y he comentado aquellos que causan un error.
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).
Tanto const
como constexpr
pueden aplicarse a variables y funciones. A pesar de que son similares entre sí, de hecho son conceptos muy diferentes.
Tanto const
como constexpr
significan que sus valores no se pueden cambiar después de su inicialización. Así por ejemplo:
const int x1=10;
constexpr int x2=10;
x1=20; // ERROR. Variable ''x1'' can''t be changed.
x2=20; // ERROR. Variable ''x2'' can''t be changed.
La principal diferencia entre const
y constexpr
es el momento en que se conocen (evalúan) sus valores de inicialización. Si bien los valores de las variables const
pueden evaluarse tanto en tiempo de compilación como en tiempo de ejecución, constexpr
siempre se evalúa en tiempo de compilación. Por ejemplo:
int temp=rand(); // temp is generated by the the random generator at runtime.
const int x1=10; // OK - known at compile time.
const int x2=temp; // OK - known only at runtime.
constexpr int x3=10; // OK - known at compile time.
constexpr int x4=temp; // ERROR. Compiler can''t figure out the value of ''temp'' variable at compile time so `constexpr` can''t be applied here.
La ventaja clave para saber si el valor se conoce en tiempo de compilación o en tiempo de ejecución es el hecho de que las constantes de tiempo de compilación pueden usarse siempre que se necesiten constantes de tiempo de compilación. Por ejemplo, C ++ no le permite especificar matrices C con las longitudes variables.
int temp=rand(); // temp is generated by the the random generator at runtime.
int array1[10]; // OK.
int array2[temp]; // ERROR.
Entonces significa que:
const int size1=10; // OK - value known at compile time.
const int size2=temp; // OK - value known only at runtime.
constexpr int size3=10; // OK - value known at compile time.
int array3[size1]; // OK - size is known at compile time.
int array4[size2]; // ERROR - size is known only at runtime time.
int array5[size3]; // OK - size is known at compile time.
Por lo tanto, las variables const
pueden definir constantes de tiempo de compilación como size1
que se pueden usar para especificar tamaños de matriz y constantes de tiempo de ejecución como size2
que se conocen solo en tiempo de ejecución y no se pueden usar para definir tamaños de matriz. Por otro lado, constexpr
siempre define constantes de tiempo de compilación que pueden especificar tamaños de matriz.
Tanto const
como constexpr
pueden aplicarse a funciones también. Una función const
debe ser una función miembro (método, operador) donde la aplicación de la palabra clave const
significa que el método no puede cambiar los valores de sus campos miembros (no estáticos). Por ejemplo.
class test
{
int x;
void function1()
{
x=100; // OK.
}
void function2() const
{
x=100; // ERROR. The const methods can''t change the values of object fields.
}
};
Un constexpr
es un concepto diferente. Marca una función (miembro o no miembro) como la función que se puede evaluar en tiempo de compilación si las constantes de tiempo de compilación se pasan como sus argumentos . Por ejemplo puedes escribir esto.
constexpr int func_constexpr(int X, int Y)
{
return(X*Y);
}
int func(int X, int Y)
{
return(X*Y);
}
int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
int array2[func(10,20)]; // ERROR - func() is not a constexpr function.
int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the ''constexpr'' function, the expression ''constexpr(10,rand())'' can''t be evaluated at compile time.
Por cierto, las funciones constexpr
son las funciones regulares de C ++ que pueden llamarse incluso si se pasan argumentos no constantes. Pero en ese caso está obteniendo los valores no constexpr.
int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can''t be evaluated at compile time.
El constexpr
también se puede aplicar a las funciones miembro (métodos), operadores e incluso constructores. Por ejemplo.
class test2
{
static constexpr int function(int value)
{
return(value+1);
}
void f()
{
int x[function(10)];
}
};
Una muestra más ''loca''.
class test3
{
public:
int value;
// constexpr const method - can''t chanage the values of object fields and can be evaluated at compile time.
constexpr int getvalue() const
{
return(value);
}
constexpr test3(int Value)
: value(Value)
{
}
};
constexpr test3 x(100); // OK. Constructor is constexpr.
int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.
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.