constexpr的作用(转)

原文: https://www.zhihu.com/question/274323507

其他参考:https://zhuanlan.zhihu.com/p/20206577

constexpr 的主要用处有

  • 拓宽「常量表达式」的范围
  • 提供显式「要求」表达式编译时(compile-time)求值的方法

为什么要拓宽「常量表达式」的范围,从原本标准库中的很多尴尬之处就可以看出:

比如我们都知道 INT_MAX 是 C 语言的遗物,C++ 则更希望大家使用 std::numeric_limits<int>::max() 来拿 int 型的上限。然而不幸的是,后者是个函数调用而不是常量,使用起来可能需要花更多性能上的心思,没有前者那么自由。

又比如标准文件流,它的构造函数可以带上这样的第二个参数:

std::fstream foo("foo.txt", std::ios::in | std::ios::out);

这个参数是 openmode 类型的,是由实现具体定义的一种 Bitmask 类型。出于类型安全的考虑,通常用枚举值实现而不是整型。但是这样一来就有个问题,同样是写 std::ios::in | std::ios::out,如果用整型的话可以作为常量表达式使用,而为了类型安全考虑换用枚举实现(尤其是重载 | 操作符)后,就再也不可能是常量表达式了。

inline openmode
operator|(openmode lhs, openmode rhs)
{
    return openmode(int_type(lhs) | int_type(rhs));
}

明明是这样简单的函数,可是对它的调用却不是常量表达式(如果是常量表达式的话就不用在运行时对这个函数进行求值计算了,而是在编译时就可以计算出结果)。这就让委员会陷入了必须在「类型安全」和「效率」里二选一的尴尬境地。

(也就是说,枚举类型可以保证类型安全(因为枚举类型取值有限,使用枚举类型就表明这个值在这里只表示这个意思,限制了使用范围,不作他用),但枚举类型的值不是常量表达式,而整形可以是常量表达式,可以在编译期就进行计算处理,不用在运行的时候计算,效率更高)

标准库里会遇到这样的问题,大家日常使用也会遇到。加之标准委员会很想借此机会把原本标准中对于「常量表达式」(尤其是整型常量表达式)复杂的定义重构简化,引入 constexpr 就很合情合理了。

(此处解释的是把「对某些简单函数的调用」加入「常量表达式」定义的出发点。其它的比如 constexpr 构造函数的用途,套路是一样的,请自行脑补。)

说到这里,「constexpr 函数」并不能和「编译时求值」划等号(但非函数的constexpr表达式是编译时求值的),它只表达了「这个函数具备这样的能力」

所以才有了 constexpr 的第二个功能:「显式『要求』表达式编译时求值」(这个时候有了它就相当于要求编译时求值,要做到这点就要求给变量初始化的表达时必须是常量表达式。把它放到变量定义前,那么用来初始化这个变量的表达式「必须」是常量表达式,否则报错。

对于constexpr 函数而言,只有同时满足

  1. 所有参数都是常量表达式
  2. 返回的结果被用于常量表达式(比如用于初始化 constexpr 数据) // 注意!是说结果被赋给的必须是常量表达式!而不是说返回的结果是常量表达式!

才会触发编译时求值。如果只有参数是常量表达式而结果不是,那么是否触发编译时求值取决于具体实现。

(就是说,函数/非函数constexpr表达式都可以编译时求值,只不过函数要满足以上2个条件。这样,我们使用constexpr函数来初始化变量时,就可以做到编译时求值,即没有类型安全问题(采用constexpr函数来初始化,而不是用整形变量),又提高了效率(编译时求值))

eg: int a = constexprFunc();


C++17 的 constexpr if 虽然严格意义上不是 constexpr 而是 if 的一部分,但既然名字里带 constexpr,也顺便提一下。

constexpr if 的主要用途是简化模板代码(这也意味着除非你是库作者或者模板狂魔,很少会用到)。很多原本需要绕弯借助 SFINAE 或者类型 Tag 来实现,需要拆成 N 个函数的功能,可以借助 constexpr if 写到一个函数里。

(还有一个用途是可以代替类似 #if 的功能,但……我觉得是邪道。)

 

 

posted @ 2023-07-15 21:57  大黑耗  阅读(81)  评论(0编辑  收藏  举报