c++11之前的编译期常量
编译器要求编译期间就要确定的,除了变量的类型,最频繁出现的就是 sizeof、枚举(enum)。
而使用时,需要用到编译期常量的则是:
- switch 语句中的case标签
- 模板(template)的模板参数
编译期常量的来源
sizeof
枚举
使用enum定义的成员,都被认为是编译期常量。
比如:
编译期常量使用
数组
数组的size,需要在编译期就确定,无论是通过常量数字确定,或是通过初始化指定,都是编译期就完成的动作。
switch lables
上述代码中,不管是 42、Color::BLUE、'z',都是在编译期就被计算认为是int类型,才能编译通过。
tmplate
template的模板参数,必须使用编译期常量进行指定,如下示例:
使用编译期常量的好处
更安全的程序
示例: 矩阵相乘
两个矩阵相乘,能够成立的前提是:
当且仅当左矩阵的列数等于右矩阵的行数,才能继续。
如果我们在编译期就知道了矩阵的size,那么我们就可以把上边的判断放在模板中完成。
这样,不同size的矩阵就变成了不同类型的变量了。
矩阵的乘法也相应变得简单了点。
使用:
使用模板确定的编译期常量,可以在编译期就能解决矩阵无法相乘的问题。
编译优化
除了直接用常数来实例化模板,还有没有其他方法来告诉编译器这个是编译期常量?
其实:
编译器能根据编译期常量来实现各种不同的优化。
比如,
如果在一个if判断语句中,其中_一个条件是编译期常量_ ,编译器知道在这个判断句中一定会走某一条路,那么编译器就会 把这个if语句优化掉,留下只会走的那一条路。
代码示例:
在上例中,编译器就会直接利用其中某一个cout语句来替换掉整个if代码块(反正运行代码的机器是32还是64位的又不会变。)
另一个可以优化的地是空间优化。
总体来说,如果我们的对象利用编译期常数来存储数值,那么我们就不用在这个对象中再占用内存存储这些数。
编译期计算
当我们通过某些手段去“胁迫”编译器,把运算任务从运行时提前到编译期,这就是编译期计算的原理。
如:
- 示例1:
由于doubleCount用于数组的声明,因此编译期会认为其是编译期常量
2. 示例2:
case标签,编译期计算出,否则编译无法通过
4. 示例3:
index用以"some"数组的下标,被认为编译期常量;
someLabel用以switch的case标签,被认为是编译期常量
使用模板进行编译期计算
实例化模板的参数必须为编译期常数
---换言之,编译器会在编译期计算 作为实例化模板参数的常量表达式。
这是一个斐波那契数列计算的示例,
通过template,将计算过程全都转移到了编译期。
Constexpr
C++11后,引入了constexpr,
以及使用constexpr标识的编译期函数( constexpr function)
constexpr function
那斐波那契数列计算的代码可以简化为如下:
如果带有constexpr的函数的参数被编译期检测为编译期常量,那么这个函数就可以自动地在编译期运行。
literal type
使用 constexpr 标识的常量,我们称为字面量(literal type)。
Literal types are the types of constexpr variables and they can be constructed, manipulated, and returned from constexpr functions
另外,需要注意的是,使用 constexpr修饰的构造函数的类,也都是literal type。
因为拥有此类构造函数的类的对象可以被constexpr 函数初始化。
使用:
tips
声明为constexpr的函数,也有一些限制:
- 函数体内不能有try块,以及任何static 和局部线程变量。
- 函数中只能调用其他 constexpr 函数
- 函数内,不能有任何运行时才会有的行为,比如抛出异常、使用new或delete操作符等
此外,
如果我们给一个函数加上一个constexpr关键字,不是说我们就把这个函数绑死在编译期上了。这个函数,是能够在运行期被复用的。
并且,如果一旦调用被认为是运行期的,那么这个函数的返回值也不再是编译期常量了,它只被当做一个正常的函数来对待。
也要注意,既然是编译期计算,那么所有运行期的检查,都不能在编译期完成。
c++17中的constexpr
constexpr lambda
对一个lambda而言,只要被捕获的变量是字面量类型(literal type),那么,整个lambda 也将表现为 字面量类型。
举例:
当一个lambda函数 在 constexpr上下文中被使用,并且它满足了 constexpr的条件,
那么无论它有没有被显式地声明为constexpr, 它仍然是 constexpr的。
参考:
当一个 lambda 表达式被显式或隐式地声明为constexpr, 它可以被转换成一个constexpr 的函数指针。
constexpr if
传统的 if-else 语句是在 执行期进行条件判断与选择的,因而在泛型编程中,无法使用。
c++17 引入 constexpr if 可以在编译期进行条件判断。
当然,这个判断的条件也是需要编译期可以计算出来的。