Difference between `constexpr` and `const`
- 我什么时候只能用其中一个?
- 我什么时候可以同时使用这两种方法?我应该如何选择一种方法?
基本含义和语法
这两个关键字都可以用于对象和函数的声明。应用于对象时的基本区别是:好的。
const 将对象声明为常量。这意味着一个保证,一旦初始化,对象的值就不会改变,编译器可以利用这个事实进行优化。它还有助于防止程序员编写代码来修改在初始化之后不打算修改的对象。好的。constexpr 声明一个对象适合在标准称为常量表达式的对象中使用。但请注意,constexpr 并不是唯一的方法。好的。
当应用于函数时,基本区别在于:好的。
const 只能用于非静态成员函数,一般不能用于函数。它保证成员函数不会修改任何非静态数据成员。好的。constexpr 既可以用于成员函数,也可以用于非成员函数,还可以用于构造函数。它声明适合在常量表达式中使用的函数。只有当函数满足某些条件(7.1.5/3,4),最重要的是(†)时,编译器才会接受它:好的。- 函数体必须是非虚拟的且非常简单:除了typedef和静态断言之外,只允许使用单个
return 语句。对于构造函数,只允许初始化列表、typedef和静态断言。(不过,也允许使用= default 和= delete 。) - 在C++ 14中,规则是更宽松的,此后在CONTHEXPR函数中允许的是:EDCOX1、8×声明、EDCOX1×9×语句、带有EDCOX1以外的标签的语句10和EDCOX1、11、TIE块、非文本类型的变量的定义、静态或线程存储持续时间的定义、定义。对不执行初始化的变量的。
- 参数和返回类型必须是文本类型(即,一般来说,非常简单的类型,通常是标量或聚合)
- 函数体必须是非虚拟的且非常简单:除了typedef和静态断言之外,只允许使用单个
常量表达式
如上所述,
它可以用于需要编译时评估的地方,例如模板参数和数组大小说明符:好的。
1
2
3
4
5
6
7template<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但是注意:好的。
声明为
constexpr 并不一定保证在编译时对其进行评估。它可以用于此目的,但也可以用于在运行时进行评估的其他地方。好的。一个对象可以适合在常量表达式中使用,而不需要声明
constexpr 。例子:好的。1
2
3
4
5int main()
{
const int N = 3;
int numbers[N] = {1, 2, 3}; // N is constant expression
}
这是可能的,因为
N 是常量,在声明时用文本初始化,满足常量表达式的条件,即使它没有声明constexpr 。好的。
那么我什么时候才能真正使用
像上面的
N 这样的对象可以用作常量表达式,而不需要声明constexpr 。这适用于以下所有对象:好的。const - 整数或枚举类型的
- 在声明时用本身是常量表达式的表达式初始化
BR/>
[这是由于§5.19/2:常量表达式不得包含涉及"lvalue to rvalue修改的子表达式,除非[…]a glvalue of integral or enumeration type[…]",感谢Richard Smith更正我之前的声明,对于所有文字类型来说,这是真的。]]好的。
要使函数适合用于常量表达式,必须显式声明
constexpr ;仅仅满足常量表达式函数的条件是不够的。例子:好的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16template<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
}
我什么时候可以/应该同时使用这两种方法,
在对象声明中。当两个关键字都引用要声明的同一对象时,这是不必要的。
1 | constexpr const int N = 5; |
是一样的好的。
1 | constexpr int N = 5; |
但是,请注意,在某些情况下,每个关键字都引用声明的不同部分:好的。
1 2 3 4 5 6 | static constexpr int N = 3; int main() { constexpr const int *NP = &N; } |
这里,
b.在成员函数声明中。在C++ 11中,EDCOX1〔0〕表示EDCOX1〔3〕,而在C++ 14和C++ 17中则不是这样。C++ 11下声明的成员函数好的。
1 | constexpr void f(); |
需要声明为好的。
1 | constexpr void f() const; |
在C++ 14下,仍然可以用作EDCOX1×3函数。好的。好啊。
基本上,它们是两个不同的概念,可以(也应该)一起使用。
概述
const 保证程序不会改变对象的值。但是,const 不保证对象进行哪种类型的初始化。考虑:
1const int mx = numeric_limits<int>::max(); // OK: runtime initialization函数
max() 只返回一个文本值。但是,由于初始值设定项是一个函数调用,因此mx 将进行运行时初始化。因此,不能将其用作常量表达式:1int arr[mx]; // error:"constant expression required"EDCOX1 4是一个新的C++ 11关键字,它需要你创建宏和硬编码文字。它还保证在某些条件下,对象会进行静态初始化。它控制表达式的计算时间。通过强制对表达式进行编译时评估,
constexpr 允许您在依赖编译时常量的任何代码中定义对时间关键型应用程序、系统编程、模板至关重要的真正常量表达式。
常量表达式函数
常量表达式函数是声明为
常量表达式函数用于替换宏和硬编码文本,而不牺牲性能或类型安全性。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) |
常量表达式对象
常量表达式对象是声明为
常量表达式对象的行为就像它被声明为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
常量表达式构造函数
常量表达式构造函数是声明为
常量表达式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
Scott Meyers关于EDCOX1的有效现代C++的技巧(4):
constexpr 对象是常量,并用编译期间已知的值初始化;- 当使用编译期间已知值的参数调用时,
constexpr 函数产生编译时结果; - 与非
constexpr 对象和函数相比,constexpr 对象和函数可以在更广泛的上下文中使用; constexpr 是对象或函数接口的一部分。
来源:使用CONTXEPR提高C++的安全性、性能和封装性。
根据Bjarne Stroustrup的《C++编程语言第四EDITNN》一书?const:大致意思是"我保证不会改变这个值"(§7.5)。主要用于指定接口,以便数据可以传递给函数而不必担心被修改。编译器执行const所作的承诺。?constexpr:大致意思是"在编译时进行评估"(§10.4)。这主要用于指定常量,以允许例如:
1 2 3 4 5 6 7 8 9 | 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 |
函数在常量表达式中可用,也就是说,在将要计算的表达式中编译器必须定义constexpr。例如:
1 | constexpr double square(double x) { return x?x; } |
要成为constexpr,函数必须相当简单:只是一个计算值的返回语句。一constexpr函数可用于非常量参数,但完成后,结果不是常量表达式。我们允许使用非常量表达式参数调用constexpr函数在不需要常量表达式的上下文中,这样我们就不必从本质上定义同一个函数有两次:一次用于常量表达式,一次用于变量。在一些地方,语言规则要求常量表达式(例如数组边界(§2.2.5,§7.3),案例标签(§2.2.4,§9.4.2),一些模板参数(§25.2),以及使用康斯特雷普)在其他情况下,编译时评估对于性能很重要。独立于性能问题,不可变(具有不变状态的对象)的概念是重要的设计问题(§10.4)。
1 2 3 4 5 | 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. |
1 2 3 4 5 6 | 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. |
了解该值在编译时或运行时是否已知的关键优势在于,只要需要编译时常量,就可以使用编译时常量。例如,C++不允许您指定具有可变长度的C数组。
1 2 3 4 | int temp=rand(); // temp is generated by the the random generator at runtime. int array1[10]; // OK. int array2[temp]; // ERROR. |
这意味着:
1 2 3 4 5 6 7 8 | 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. |
因此,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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. } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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. |
顺便说一下,EDCOX1的1个函数是即使通过了非常量参数也可以调用的规则C++函数。但在这种情况下,您将得到非constexpr值。
1 2 | 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. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class test2 { static constexpr int function(int value) { return(value+1); } void f() { int x[function(10)]; } }; |
更"疯狂"的样本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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. |
正如@ 0x499 602D2已经指出的那样,EDCOX1〔0〕仅保证在初始化之后不能改变值,因为EDCOX1×4(在C++ 11中引入)保证变量是编译时常数。考虑以下示例(来自learncpp.com):
1 2 3 4 5 6 | 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 |
下面是一个很好的例子:
1 2 3 4 5 6 7 | 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 } |
上面的代码片段编译得很好,我已经对导致它出错的代码进行了注释。