constexpr (C++)
关键字 constexpr
是在 C++11 中引入的,并在 C++14 中进行了改进。 它表示 constant(常数)表达式。 与 const
一样,它可以应用于变量:如果任何代码试图 modify(修改)该值,将引发编译器错误。 与 const
不同,constexpr
也可以应用于函数和类 constructor(构造函数)。 constexpr
指示值或返回值是 constant(常数),如果可能,将在编译时进行计算。
每当需要 const 整数时(如在模板自变量和数组声明中),都可以使用 constexpr
整数值。 如果在编译时(而非运行时)计算某个值,它可以使程序运行速度更快、占用内存更少。
为了限制计算编译时 constant(常数)的复杂性及其对编译时间的潜在影响,C++14 标准要求 constant(常数)表达式中所涉及的类型为文本类型。
语法
constexpr
literal-typeidentifier = constant-expression;
constexpr
literal-typeidentifier{constant-expression};
constexpr
literal-typeidentifier(params);
constexpr
ctor(params);
参数
params
一个或多个参数,每个参数都必须是文本类型,并且本身必须是 constant(常数)表达式。
返回值
constexpr
变量或函数必须返回文本类型。
constexpr 变量
const
与 constexpr
变量之间的主要 difference(区别)是,const
变量的初始化可以推迟到运行时进行。 constexpr
变量必须在编译时进行初始化。 所有的 constexpr
变量都是 const
。
- 如果一个变量具有文本类型并且已初始化,则可以使用
constexpr
声明该变量。 如果初始化是由 constructor(构造函数)performed(执行)的,则必须将 constructor(构造函数)声明为constexpr
。 - 当满足以下两个条件时,引用可以被声明为
constexpr
:引用的对象是由 constant(常数)常数表达式初始化的,初始化期间调用的任何隐式转换也是 constant(常数)表达式。 constexpr
变量或函数的所有声明都必须具有constexpr
specifier(说明符)。
constexpr float x = 42.0;
constexpr float y{108};
constexpr float z = exp(5, 3);
constexpr int i; // Error! Not initialized
int j = 0;
constexpr int k = j + 1; //Error! j not a constant expression
constexpr 函数
constexpr
函数是在使用需要它的代码时,可在编译时计算其返回值的函数。 使用代码需要编译时的返回值来初始化 constexpr
变量,或者用于提供非类型模板自变量。 当其自变量为 constexpr
值时,函数 constexpr
将生成编译时 constant(常数)。 使用非 constexpr
自变量调用时,或者编译时不需要其值时,它将与正则函数一样,在运行时生成一个值。 (此双重行为使你无需编写同一函数的 constexpr
和非 constexpr
版本。)
constexpr
函数或 constructor(构造函数)通过隐式方式 inline
。
以下规则适用于 constexpr 函数:
constexpr
函数必须只接受并返回文本类型。constexpr
函数可以是递归的。- 可以是fore C++20,函数
constexpr
不能是虚拟的,当封闭类具有任何虚拟基类时,不能定义const一个constexpr
ructor。 在 C++20 及更高版本中,constexpr
函数可以是虚函数。 指定/std:c++20
或更高版本的编译器选项时,Visual Studio 2019 版本 16.10 及更高版本支持constexpr
虚函数。 - 主体可以定义为
= default
或= delete
。 - 正文不能包含如何
goto
语句或try
块。 - 可以将非
constexpr
模板的显式专用化声明为constexpr
: constexpr
模板的显式专用化不需要同时也是constexpr
:
以下规则适用于 Visual Studio 2017 及更高版本中的 constexpr
函数:
- 它可以包含
if
和switch
语句,以及所有循环语句,包括for
、基于范围的for
、while
、和 do-while。 - 它可能包含局部变量声明,但必须初始化该变量。 它必须是文本类型,不能是
static
或线程本地的。 本地声明的变量不需要是const
,并且可以变化。 constexpr
非static
成员函数不需要通过隐式方式const
。
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
}
提示
在 Visual Studio 调试器中,在调试非优化调试版本时,可以看出
constexpr
函数是否是通过在其内部放置一个断点在编译时计算的。 如果命中该断点,则在运行时调用该函数。 如果未命中,则在编译时调用该函数。
extern constexpr
/Zc:externConstexpr 编译器选项使编译器将外部链接应用于使用 extern constexpr声明的变量。 在早期版本的 Visual Studio 中,默认情况下或者 specified(指定)了 /Zc:externConstexpr- 时,即使使用关键字 extern
,Visual Studio 也会将内部链接应用于 constexpr
变量。 从 Visual Studio 2017 Update 15.6 开始,可以使用 /Zc:externConstexpr 选项,默认情况下它处于关闭状态。 /permissive- 选项不会启用 /Zc:externConstexpr。
示例
下面的示例展示了 constexpr
变量、函数和用户定义类型。 在 main()
的最后一个语句中,constexpr
成员函数 GetValue()
是一个运行时调用,因为不需要在编译时知道该值。
C++复制
// constexpr.cpp
// Compile with: cl /EHsc /W4 constexpr.cpp
#include <iostream>
using namespace std;
// Pass by value
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
}
// Pass by reference
constexpr float exp2(const float& x, const int& n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp2(x * x, n / 2) :
exp2(x * x, (n - 1) / 2) * x;
}
// Compile-time computation of array length
template<typename T, int N>
constexpr int length(const T(&)[N])
{
return N;
}
// Recursive constexpr function
constexpr int fac(int n)
{
return n == 1 ? 1 : n * fac(n - 1);
}
// User-defined type
class Foo
{
public:
constexpr explicit Foo(int i) : _i(i) {}
constexpr int GetValue() const
{
return _i;
}
private:
int _i;
};
int main()
{
// foo is const:
constexpr Foo foo(5);
// foo = Foo(6); //Error!
// Compile time:
constexpr float x = exp(5, 3);
constexpr float y { exp(2, 5) };
constexpr int val = foo.GetValue();
constexpr int f5 = fac(5);
const int nums[] { 1, 2, 3, 4 };
const int nums2[length(nums) * 2] { 1, 2, 3, 4, 5, 6, 7, 8 };
// Run time:
cout << "The value of foo is " << foo.GetValue() << endl;
}
要求
Visual Studio 2015 或更高版本。