C++-constexpr关键词

简介

constexpr 为 C++11 提供的说明符,目的是将运算尽量放在编译阶段,而不是运行阶段

声明对象或非静态成员函数 (C++14 前)时使用 constexpr 说明符则同时蕴含 const

声明函数或静态成员变量 (C++17 起)时使用 constexpr 说明符则同时蕴含 inline

如果一个函数或函数模板的某个声明拥有 constexpr 说明符,则其所有声明都必须含有该说明符

constexpr变量

要求

详情见cppreference-constexpr

一句话而言:声明为constexpr的变量一定是常量,而且必须用常量表达式初始化

详解

1. 声明

声明 constexpr 变量时用到的类型被称为字面值类型,声明为 constexpr 的变量一定是一个 const 变量,而且必须用常量表达式初始化

1
2
3
constexpr int num = 20;         //num为常量表达式
constexpr int incNum = num + 1; //incNum为常量表达式
constexpr int size = size(); //size()为constexpr函数

2. constexpr指针

需要注意的是,与 const 修饰指针不同,一个指针被定义为 constexpr ,关键字仅对指针有效,与指针所指的对象无关

constexpr 指针的初始值受到严格的限制。一个 constexpr 指针的初始值必须是 nullptr 或者 0 ,或者是像先前说的一样是存储某个固定地址的对象

1
2
const int* p = nullptr;     //p为指向整形常量的指针
constexpr int* q = nullptr; //q为指向整型的常量指针

constexpr函数

要求

详情见cppreference-constexpr

一句话而言:函数的返回类型所有形参必须为字面值类型(声明 constexpr 变量时用到的类型),且函数体中必须有且只有一条 return 语句,但不产生实际代码的语句可以在 constexpr 函数中使用(using,static_assert,typedef, …)

详解

1. 实参传入

当 constexpr 所修饰的函数实参是在运行时才能传入,那么这个 constexpr 是无效的,相当于普通函数

2. 有关返回值

我们允许 constexpr 函数的返回值并非一个常量,而可以是另一个 constexpr 函数的返回值,即为字面值类型即可

使用这个特性,有一些递归函数可以通过 O(n) 的复杂度完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//在开启内联优化的编译器上,使用 constexpr 修饰函数和不适用完成斐波拉契函数的递归实现区别
//这个实现的复杂度等同于迭代的方法,基本上为 O(n)
constexpr long int fib1(int n)
{
return (n <= 1)? n : fib1(n-1) + fib1(n-2);
}
//熟悉递归函数就不难证明下面这个函数的时间复杂度为 O(2^n)
long int fib2(int n)
{
return (n <= 1)? n : fib2(n-1) + fib2(n-2);
}
int main
{
constexpr long int res1 = fib1(10);
long int res2 = fib2(10);
}

反汇编

1
2
3
4
5
6
7
8
...
constexpr long int res = fib1(10);
00007FF7F0431ACA mov dword ptr [res],37h
long int res2 = fib2(10);
00007FF7F0431AD1 mov ecx,0Ah
00007FF7F0431AD6 call fib2 (07FF7F04311EAh)
00007FF7F0431ADB mov dword ptr [res2],eax
...

不难看出,res1 的值是直接通过已经计算出来的值赋的,而 res2 的则是通过调用函数

if constexpr

C++17中引入了一个新用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if constexpr(condition)
...

if constexpr(condition)
...
else
...

if constexpr(condition1)
...
else if constexpr(condition2)
...
else
...

condition所需满足以下两个条件:

  1. 编译期就能算出true或者false。
  2. 或是通过类型转换为true或者false。
    两个条件只要成立,编译期便会照着是否满足if中条件来选择编译对应块中的内容,可以粗略的认为另一个板块被摒弃了。