学习内容
本节学习constexpr含义以及在各个版本中的应用场景,后续请关注 学习C++11/14/17/20/23关键词版本更替 ,将持续更新~~
constexpr 修饰的变量、结构化绑定或函数的值可以出现在常量表达式中
constexpr声明的实体的值可以在编译期求值。用于对象声明或非静态成员函数的constexpr修饰隐含const限定(不允许后期修改)
用于函数或静态数据成员的第一个声明中的constexpr隐含inline(C++17,解决跨capp编译重复定义问题)。 如果函数或函数模板的任何声明具有constexpr,则每个声明都必须包含此说明符(某些函数的声明与定义处于两个文件,两个文件对于该函数或模板的操作都必须带有constexpr ; 又或者跨文件的声明,A.h文件声明,B.h文件使用前再次声明)
constexpr 声明的变量需要满足以下所有条件:
- 在初始定义时必须直接分配内存并初始化(编译期间运行期间就可计算结果的表达式,必须确定类型以及值)
- 定义的类型必须是字面类型(int ,double , std::array等目前常用的类型均属于字面类型),或满足特定条件的自定义类(有constexpr构造/析构,成员均为字面型类型)
- 满足constexpr变量销毁行为的,首先其定义的对象的类型支持constexpr析构函数,或者在同一个常量表达式内部的数组(数组元素需支持销毁行为)
- 编译期跨翻译单元(每个.cpp文件为一个翻译单元,包括该文件包含的所有头文件)编译防止同一个constexpr变量被编译出不同的值
constexpr int a = 10;
constexpr array<int , 3> arr = { 1 ,2 ,3 };
extern constexpr int b ; //error constexpr无法使用extern来修饰
constexpr int c ; / /error ,一经定义必须直接初始化
constexpr vector<int> vec = { 1 ,2 }; //error ,类型非字面型
//error ,tmp是运行时赋值,但是value的赋值依赖于tmp的值,报错
int tmp = 10;
constexpr int value = tmp;
constexpr声明的函数需要满足以下所有条件:
1、当构造函数或析构函数是constexpr时,其所属的类不能有任何的虚基类;如果是constexpr声明的函数,那么这个函数不能是虚函数
class A { constexpr A () = default ; }
class B : virtual A { constexpr B() {} } //error 不允许有任何的虚基类
class C {
virtual constexpr void func()[} //error , constexpr修饰的函数不能是虚函数 }
2、返回类型(当存在返回值)必须是字面类型;每个参数必须是字面类型;函数不能是协程(C++20引入新概念) 字面类型:支持在编译期静态开辟内存并初始化
constexpr int add ( int a, int b)
{
return a + b;
}
3、用constexpr修饰函数的函数体限制:
- 如果构造或者析构函数,则可以是 = default / = delete
- 或者是一个 复合简单语句,且仅包含:
- 空语句(空函数体)
- static_assert声明
- typename、using等别名声明
- 如果不是构造函数,必须恰好有一个return语句
从c++20起,复合语句不能包含:
- goto语句
- 除case /default之外的标签
- try块
- 内联汇编
- 未初始化变量定义
- 非字面类型变量定义
- 静态/线程存储器变量的定义
// C++11 合法函数体,简单的return语句
constexpr int square ( int a ,int b)
{
return a * b ;
}
//C++20合法 允许多条语句
constexpr int count (int i )
{
if( i * i > 10 ) return i;
else return –i;
}
4、非模板函数的强制要求 非模板的constexpr函数必须是contexpr-suitable适配(需要满足第3个条件)
5、模板函数的额外要求 如果是一个非模板化的constexpr(普通函数), 参数必须是字面类型 对于特化版的模板函数,仍然需要满足第3个条件
template<typename T> //T必须是字面类型
constexpr T square( T x) { return x * x; }
6、调用行为 constexpr函数可以出现在常量表达式中
constexpr int add ( int a , int b) { return a + b ; }
constexpr int res = add(3,4); //编译期确定
constexpr声明的构造函数满足以下所有条件
用于在编译期创建对象,除了满足普通的constexpr函数要求外,还需要额外满足: 1、函数体必须是delete,或者满足以下附加条件:
- 联合类型(union):如果是带变体成员的联合,必须恰好初始化一个成员;如果是类联合(class或struct定义的类型)中的匿名联合,每个匿名联合必须恰好初始化一个成员(C++20前)
类联合中的匿名联合: * 没有名字:不需要通过联合名来访问其成员,直接通过类的对象访问 * 内存复用:和普通联合一样,匿名联合的所有成员共享同一块内存 * 成员可见性:匿名联合的成员会直接成为类的成员,可以直接访问
struct A
{
enum Type { INT , DOUBLE ,STRING} type;
//类联合的匿名联合
union {
int i;
double d;
const char* s; };
constexpr A ( int val) : type (INT) , i (val) {}
constexpr A ( double val) : type (DOUBLE) , d (val) {}
constexpr A ( const char* val) : type (STRING) , s (val) {}
};
- 非静态成员与基类:所以非静态成员和基类对象必须被初始化
- 委托构造函数:目标构造函数也必须是constexpr
- 非委托构造函数:用于初始化成员和基类的所有构造函数,也必须都是constexpr构造函数 2、所属类不能有任何虚基类
//普通结构体 constexpr构造函数
struct Point {
int x;
int y;
constexpr Point( int x_ ,int y_ ) : x (x_) , y (y_){}
};
Point Pt( 2, 3) ; //编译期创建对象
//带基类的constexpr构造 ,基类也必须是constexpr构造
struct Base{
int vaule;
constexpr Base( int v ) : value ( v) {} };
struct Child: Base{
int data;
constexpr Child(int v ,int d) : Base(v) , data(d){} };
Child c ( 10 ,2 );
//联合类型
union Used
{
int id;
double number;
constexpr Used( int d ) : id ( d ) {}
constexpr Used(double num ) : number( num ) {}
};
constexpr Used u (4);
constexpr声明的析构函数满足以下条件
在C++20前,不能显式声明为constexpr,但平凡析构函数可以在常量表达式中隐式调用 从C++20起,允许显式声明为constexpr析构函数,但仍需满足额外条件 1、 所属类不能有任何虚基类 对于类类型或数组的每个子对象,其类型也必须拥有constexpr析构函数
注意
由于noexcept运算符对于常量表达式始终返回true, 因此它可以用于检查constexpr函数的特定调用是否采用常量表达式分支
int func()
{
return 1;
}
constexpr int f(int i)
{
return func(); //error ,f 不能调用非constexpr函数
}
2、对于非字面类型,允许使用constexpr函数。例如std::shared_ptr默认构造函数是constexpr,允许常量初始化。 引用变量可以声明为constexpr(初始化器必须是引用常量表达式)
static constexpr int const& x = 42;
尽管try块和内联汇编在constexpr函数中是允许的,但在常量表达式中仍然禁止抛出未捕获的异常或执行汇编
C++23放宽了函数体限制:constexpr函数体中可以写try/catch和内联汇编 但是在常量表达式中调用时: 不允许抛出未被捕获的异常 不允许执行内联汇编
constexpr int div( int a ,int b)
{
try
{
if( b == 0 ) throw "div by zero" ;
return a/ b;
}catch(...) { return 0 ;}
}
constexpr int r1 = div(10,2);
constexpr int r2 = div(10,0); //异常捕获
如果变量具有常量销毁(编译期就可完成对象销毁),则无需生成机器码调用其析构函数,即使其析构函数不是平凡的
struct Person
{
int id;
constexpr Person(int i) : id(i) {}
//非平凡析构函数
constexpr ~Person(){}
};
constexpr int GetId()
{
Person p (32);
return p.id; //直接返回后销毁,无需生成析构机器码
}
constexpr int ids = GetId(); // 编译期计算,无析构函数调用
非lambda、非特殊成员和非模板化的constexpr函数不能隐式称为即时函数。用户需显示将其标记为consteval才能使此类预期函数定义格式正确
//非lambda,非特殊成员,非模板化
constexpr int normalFunc() { return 32; } //error
consteval int rightFunc() { return 32; } //ok
C++11 初次引入
仅支持编译期常量表达式、简单函数(单return)、构造函数 constexpr变量必须是字面类型(不支持运行时动态分配内存),且初始化表达式必须是常量表达式 仅支持初始化所有成员的简单构造函数,不支持委托构造(一个构造函数调用另一个构造函数) 不支持显式声明constexpr析构,仅平凡析构可隐式调用
C++14 放宽函数体限制
允许函数体包含多条语句、循环、分支、局部变量(必须初始化且为字面类型) constexpr变量允许函数内部定义,支持constexpr变量赋值和修改(编译器) 支持constexpr函数返回字面类型的对象
C++17 扩展容器与跨翻译单元支持
容器支持std::array、std::string_view容器成为字面类型,支持constexpr初始化和操作 inline constexpr:允许全局constexpr变量加Inline修饰,实现跨翻译单元共享,解决一致性问题 constexpr if: 引入编译器条件分支,进一步增强编译器逻辑能力 构造函数增强:支持委托函数为constexpr
inline constexpr int VAL = 100; //跨TU共享的constexpr变量
C++20 全面强化编译期能力
constexpr析构函数:允许显式声明constexpr析构,支持编译器对象销毁 动态内存分配:在常量表达式中允许使用new/delete(仅编译器,运行时无残留) 容器扩展:std::vector、std::string等动态容器支持部分constexpr操作(构造、元素访问) lambda表达式:支持constexpr lambda,可在编译期运行 虚函数限制:constexpr函数不能是虚函数的限制被移除(仍然不能有虚基类)
struct A {
int data;
constexpr A (int d ) : data(d) {}
constexpr ~A() = default; //显式constexpr析构 };
constexpr auto CreateA()
{
A * pA = new A(10); //编译期动态分配
int val = pA->data;
delete pA;
pA = nullptr;
return val;
}
constexpr int val = CreateA();
C++23 进一步放宽限制
初始化规则放宽:constexpr变量初始化从常量表达式放宽为可初始化,允许静态初始化阶段完成初始化 函数体扩展:允许constexpr包含try/catch块 模板增强:对模板化constexpr函数的特化检查更灵活 类型限制放宽:返回值和参数的字面类型限制进一步放宽,支持更多场景
static int val = 10;
constexpr int val = val; //允许静态初始化阶段的变量初始化


![P11246 [GESP202409 六级] 小杨和整数拆分-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108100631-6af049a75855c-220x150.png)

