关键字 Constexpr
[TOC]
1. constexpr
在C++中,constexpr 是一个类型修饰符,于C++11标准被引入,并在随后的C++14和C++17标准中得到了扩展和增强。1
constexpr 修饰的对象或函数指示编译器该对象或函数的值可以在编译时被确定,这使得它们可以用在需要编译时常量表达式的上下文中,例如数组大小、枚举、模板参数等。constexpr提供了更多编译期计算能力,增强了代码的灵活性和性能。2
2. 用法
(1) constexpr 对象
constexpr 变量或对象必须立即初始化,并且其初始化表达式必须是一个常量表达式。这意味着编译器必须能够在编译时计算出它的值。
1
2
constexpr int max_size = 100; // 编译时常量
int array[max_size]; // 使用常量作为数组大小
在这个例子中,max_size 被定义为一个编译时常量,并且被用作数组 array 的大小。
也就是说,当你需要定义一个在编译时就已知其值的常量时,你可以使用 constexpr 来声明它,这样可以提高代码的清晰度与执行效率。
(2) constexpr 函数
在C++11中,constexpr 函数必须满足严格的限制条件,它们只能包含单一的返回语句。但是在C++14中,这些限制被放宽,允许函数体包含更多类型的语句,只要编译器能够在编译时计算出函数的返回值。
1
2
3
constexpr int factorial(int n) {
return n <= 1 ? 1 : (n * factorial(n - 1));
}
在这个例子中,factorial 是一个递归的 constexpr 函数,它在编译时计算阶乘。
constexpr 函数在调用时如果使用的是常量表达式作为参数,那么它将在编译时计算结果;如果使用的是变量或非常量表达式,它将在运行时计算结果。
简单来说,可以使用 constexpr 函数来执行编译时计算,这有助于减少运行时开销,并且能在编译时捕获潜在的错误。
(3) constexpr 构造函数
constexpr 构造函数允许对象在编译时被创建。这对于用户定义的字面量类型或者用于模板元编程的类非常有用。
1
2
3
4
5
6
7
struct Point {
constexpr Point(double xVal, double yVal) : x(xVal), y(yVal) {}
double x;
double y;
};
constexpr Point p(2.0, 3.0); // 创建一个编译时常量对象
在这个例子中,Point 结构体有一个 constexpr 构造函数,它允许在编译时创建 Point 对象。
(4) if constexpr
在C++17中引入的 if constexpr 是一个条件编译语句,它允许编译器在编译时根据条件表达式决定是否编译某段代码。
这是模板元编程中特别有用的特性,它可以基于模板参数进行条件编译,从而在编译期间就消除不必要的分支和相关代码。
传统的 if 语句会在运行时评估条件,而 if constexpr 会在编译时评估。
如果 if constexpr 中的条件结果为 false,相关的代码块就会在编译期间被丢弃,不会成为程序的一部分。这就意味着该代码块中可能存在的编译错误也不会出现,只要这部分代码不会被编译。
if constexpr 在模板代码中尤其有用,因为它允许基于模板参数的类型或值编写更简洁和更具可读性的代码。例如,可以使用 if constexpr 来区分一个模板函数对不同类型的处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <type_traits>
template <typename T>
auto get_value(T t)
{
if constexpr (std::is_pointer_v<T>)
{// 如果T是指针类型,则解引用指针
return *t;
}
else if constexpr (std::is_pointer_v<T>)
{// 如果T是指针类型,则解引用指针
return *t;
}
else
{// 否则直接返回值
return t;
}
}
在上面的例子中,if constexpr 使得函数 get_value 可以根据传入的类型是不是指针来选择不同的代码路径。如果 T 是一个指针类型,编译器会编译解引用指针的那个分支;如果不是,编译器则编译直接返回值的分支。对于不满足条件的分支,即使里面包含错误或者不适用于某些类型的代码,也不会引起编译错误,因为不会被实例化。
使用 if constexpr 可以有效减少模板实例化时的代码膨胀,优化编译时间,并且允许编写出更清晰、逻辑上正确的代码。
3. constexpr 功能演化
(1) 在C++11中:
constexpr变量必须立即初始化。constexpr函数体只能包含一个返回语句。constexpr函数和构造函数不能声明任何类型的变量,也不能使用任何类型的循环。
(2) 在C++14中:
- 放宽了对
constexpr函数的要求,允许它们有多个语句,但所有这些语句在编译时都必须可解析。 - 允许
constexpr函数有更多的控制流,如if语句和循环语句。 - 允许
constexpr函数有局部变量,只要它们的类型也是字面量类型。
(3) 在C++17中:
constexpr函数可以使用更广泛的语言特性,包括局部变量、循环以及其他复杂的语句。if constexpr语句的引入,允许在模板中根据模板参数条件编译代码。- 静态成员变量也可以被声明为
constexpr。
(4) 通用规则:
constexpr表达式必须总是能够在编译时求值,这意味着所有涉及的变量、函数等都必须足够简单,以便编译器可以计算它们的值。constexpr函数中如果使用了非constexpr表达式作为参数,那么这个函数可以在运行时执行。
5. const 与 constexpr 的区别
在C++中,const和constexpr都用于定义常量,但有重要区别。const适用于在运行时确定的常量,而constexpr用于在编译时确定值的常量,可用于数组大小、枚举等。constexpr提供了更多编译期计算能力,增强了代码的灵活性和性能。
在C++中,constexpr和const都用于定义常量,但它们有着不同的用途和行为:2
- const: 用于定义在运行时期确定值的常量,适用于各种常量,但无法用于在编译时期确定值的计算。
- constexpr: 用于定义在编译时期确定值的常量,适用于需要在编译时期计算的场景,如数组大小、枚举值等。
const用于运行时期常量,而constexpr则强调在编译时期常量,并提供更多的编译期计算能力。
6. consteval
(1)consteval 的作用,和 constexpr 的区别?
consteval 和 constexpr 都是 C++ 中用于编译时计算的关键字,但它们有一些重要的区别。3
(2)consteval
- 引入时间:
consteval是在 C++20 引入的。 - 含义:
consteval指定的函数必须在编译时进行求值。 - 限制: 如果一个
consteval函数在编译时不能被求值,那么会产生一个编译错误。 - 用途:
consteval用于创建那些一定需要在编译时求值的函数,比如用于元编程或者生成编译时常量。
(3)constexpr
- 引入时间:
constexpr是在 C++11 引入的,后续的标准中进行了扩展。 - 含义:
constexpr指示一个函数或对象可能在编译时求值。 - 灵活性:
constexpr函数或对象可以在编译时也可以在运行时求值。如果编译器能够在编译时求值,它通常会这样做;否则,该函数或对象就会在运行时求值。 - 用途:
constexpr用于创建可以在编译时或运行时求值的函数或对象,提供了更大的灵活性。
(4)区别总结
- 编译时求值的强制性:
consteval强制其函数在编译时求值,而constexpr则不强制。 - 应用场景:
consteval用于那些必须在编译时求值的场景,而constexpr更适用于那些编译时求值能带来性能优化但非必需的情况。 - 错误处理: 如果
consteval函数不能在编译时求值,将导致编译错误;而constexpr函数则会退化为普通函数,在运行时求值。
简而言之,consteval 保证了函数必定在编译时求值,而 constexpr 提供了在编译时求值的可能性,但并非强制性的。