C++中的constexpr

C++中的constexpr
xucanxxC++中的constexpr
https://blog.csdn.net/m0_52902391/article/details/120308866
https://zhxilin.github.io/post/tech_stack/1_programming_language/modern_cpp/cpp17/constexpr/#c14-constexpr
常量表达式
值不会改变且在编译期就能得到计算结果的表达式。
字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
constexpr 与 const
在定义常量时,const 和 constexpr 是等价的,都可以在程序的编译阶段计算出结果 都为常量表达式
但是const可以修饰函数的传入参数为只读(并非常量),而constexpr无法修饰
1 | // error |
用constexpr修饰函数
constexpr 并不能修改任意函数的返回值,这些函数成为常量表达式函数时,必须要满足以下几个条件:
(这些规则不仅对应普通函数适用,对应类的成员函数也是适用的)
1.函数必须要有返回值,并且 return 返回的表达式必须是常量表达式。
1 | // error,不是常量表达式函数 |
函数 func1() 没有返回值,不满足常量表达式函数要求
函数 func2() 返回值不是常量表达式,不满足常量表达式函数要求
2.整个函数的函数体中,不能出现非常量表达式之外的语句(using 指令、typedef 语句以及 static_assert 断言、return 语句除外)。
1 | // error |
在c++11中constexpr函数只能有一个return语句,不支持多个独立语句,最多也只能通过逗号表达式或三目表达式来表达。
C++14开始,constexpr函数可以使用多行语句编写
修饰模板函数
C++11 语法中,constexpr 可以修饰函数模板,但由于模板中类型的不确定性,因此函数模板实例化后的模板函数是否符合常量表达式函数的要求也是不确定的。
如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。
1 |
|
在上面示例程序中定义了一个函数模板 display(),但由于其返回值类型未定,因此在实例化之前无法判断其是否符合常量表达式函数的要求:
struct Person ret = dispaly(p); 由于参数 p 是变量,所以实例化后的函数不是常量表达式函数,此时 constexpr 是无效的
constexpr int ret1 = dispaly(250); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的
constexpr struct Person p2 = dispaly(p1); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的
修饰构造函数(编译期的对象构造)
如果想用直接得到一个常量对象,也可以使用 constexpr 修饰一个构造函数,这样就可以得到一个常量构造函数了。常量构造函数有一个要求:构造函数的函数体必须为空,并且必须采用初始化列表的方式为各个成员赋值。
1 |
|
constexpr lambda表达式
C++17对constexpr的适用范围再次进行扩展,已经可以运用在lambda表达式上了。我们曾经在关于闭包的文章中讲过,编译器在处理lambda表达式的时候,会自动为其合成一个闭包类型(仿函数),这个闭包类型包含一个operator()的重载操作符,lambda表达式的捕获列表将转换成该仿函数的成员变量,最后lambda表达式会被这个闭包类型的对象替换。
以下面这个例子为示范:
1 |
|
编译器会将其转换为如下形式:
1 |
|
C++17对lambda表达式做了如下改进:
若lambda表达式捕获的变量是字面量类型(literal type),则整个lambda表达式也将表现为字面量类型。
增加constexpr lambda表达式语法。
若一个lambda表达式满足constexpr函数的要求,即使没有明确声明为constexpr,编译器也会将其推导为constexpr lambda表达式。
constexpr lambda表达式的显式语法如下:
1 | [capture_list] (params_list) constexpr -> return_type {function_body} |
在完整体的lambda表达式中,constexpr添加到参数列表和返回值类型之间,举个例子:
1 | void foo() { |
constexpr lambda表达式和constexpr函数一样,需要满足的条件
- 返回值的类型必须是字面量类型
- 参数类型必须是字面量类型
- 函数体还需满足:
- 没有使用inline语句
- 没有使用goto或label
- 没有使用try…catch语句
- 没有声明或使用非字面量类型的变量或使用thread local storage
只要满足上述提到的条件,即使没有将lambda声明为constexpr,编译器也会自动将其视为constexpr lambda表达式
且与constexpr函数一样,constexpr lambda表达式中,如果参数或捕获列表的参数不是编译期常量,则constexpr lambda表达式会退化为普通的lambda表达式
当一个lambda表达式被显式或隐式地声明为constexpr,则它可以被转换为一个constexpr的函数指针
constexpr if 编译期分支选择
传统的if-else语句是在运行期进行判断和选择的,无法运用在编译期,所以在泛型编程中,无法使用if-else来直接做一些判断。在C++17之前,只能被拆分为一个泛型版本和一个特化版本
1 | //C++17之前的写法 |
C++17引入了constexpr if,支持在编译期进行判断,即在编译期就可以直接知道结果,可以广泛应用于泛型编程。以上例子使用C++17实现则可以将两步合二为一
1 | template <typename T, typename... Ts> |
优化std::enable_if写法
constexpr if在泛型编程中的一个应用是,可以替代冗长的std::enable_if的写法。在C++17之前,需要为了不同类型条件去写各式各样的特化模版,利用SFINAE加上std::enable_if,会写得非常复杂
1 | //C++17之前 |
上面的例子中,else不可省略,否则会产生编译器推导错误,无法编译通过。
编译器在做优化时,甚至会把没有使用的分支省略掉,当然另一个分支必须支持C++语法,而老的C++标准即使使用了if,另一个分支也会被编译。对于constexpr if,编译器只会实例化条件通过的子句。由于返回值类型我们可以用auto让编译器自动推导,所以每个constexpr if子句的返回值类型也不需要完全一致。








