文章目录
- `constexpr`
- `constexpr` 值
- `constexpr` 函数和lambda
- `if constexpr`编译时分支
- `consteval`
- `constinit`
constexpr
constexpr
是C++中的一个关键字,用于定义常量表达式。这些表达式在编译时就可以计算出结果,而不是在运行时。
constexpr
值
constexpr
可以修饰变量. 此时,变量必须在编译时就能确定其值.
// 初始化常量表达式
constexpr int min_size = 10; // OK, 字面量初始化
constexpr int max_size = min_size + 10; // OK, 常量表达式
min_size = 20; // Error, 常量不能被修改
可以像使用常量一样使用 constexpr
变量.
constexpr int size = 10;
int c_arr[size] = {0}; // OK
std::array<int, size> array = {0}; // OK
除了整型, 其他类型也可以使用 constexpr
修饰.
constexpr double root_of_2 = 1.41421356237;
constexpr const char* hello = "Hello, World!";
constexpr std::string_view str_view{hello};
如果你想定义一个constexpr
的类变量. 你需要确保类的构造函数是constexpr
的.
// constexpr 类实例
class Point {public:constexpr Point(int x, int y) : x_(x), y_(y) {}constexpr int x() const { return x_; }constexpr int y() const { return y_; }private:int x_;int y_;
};
constexpr Point origin{0, 0};
constexpr Point dst{1, 1};
constexpr
函数和lambda
constexpr
修饰函数表示该函数在编译时就能计算出结果.
constexpr int square(int x) { return x * x; }int main() {constexpr int a = 3;constexpr int b = 4;constexpr int c = square(a) + square(b); // OK, a 和 b 是常量int d = square(5); // OK, d 不要求是常量int e = 3;constexpr int f = square(e); // Error, e 不是常量. 参数必须是常量auto abs = [](int x) constexpr -> int { return x < 0 ? -x : x; };constexpr int g = abs(-1); // OKreturn abs(0);
}
if constexpr
编译时分支
if constexpr
引入的主要原因是为了提高C++模板编程的能力和效率,具体包括:
-
编译时分支决策:
if constexpr
允许在编译时根据模板参数或其他编译时可知的条件进行条件分支,这意味着可以在编译时决定哪些代码会被编译进最终的程序中。这对于模板元编程尤其重要,因为它允许基于类型特性进行条件编译,从而避免了运行时的分支判断,提高了程序的效率。 -
简化模板代码:在引入
if constexpr
之前,实现基于类型的条件编译通常需要使用模板特化或SFINAE(替换失败不是错误)技术,这些技术不仅代码复杂,而且对于初学者来说难以理解。if constexpr
简化了这一过程,使得基于类型条件的代码分支更加直观和易于编写。 -
避免无效代码实例化:在模板编程中,某些代码路径可能对于特定的模板参数是无效的。使用
if constexpr
可以确保只有有效的代码路径会被实例化,从而避免编译错误。 -
优化性能:由于
if constexpr
在编译时就决定了代码的执行路径,它可以帮助编译器生成更优化的代码。对于不满足条件的分支,由于它们根本不会被编译,因此可以减少最终程序的大小,并提高运行时性能。 -
增强代码可读性和维护性:
if constexpr
使得条件编译的意图更加明显,提高了代码的可读性。同时,由于减少了模板特化和SFINAE的需要,也使得代码更容易维护。
#include <iostream>
#include <string>
#include <type_traits>template <typename T>
void process(const T& value) {if constexpr (std::is_integral<T>::value) {std::cout << "Processing integral type: " << value << std::endl;} else if constexpr (std::is_floating_point<T>::value) {std::cout << "Processing floating point type: " << value << std::endl;} else {std::cout << "Processing other types" << std::endl;}
}template <typename T>
void printPositiveOrNegative(const T& value) {if constexpr (std::is_signed<T>::value) {if (value < 0) {std::cout << "Negative" << std::endl;} else {std::cout << "Positive or zero" << std::endl;}} else {std::cout << "Always positive or zero" << std::endl;}
}template <int N>
constexpr int factorial() {if constexpr (N == 0) {return 1;} else {return N * factorial<N - 1>();}
}template <typename T>
void printOrCompute(const T& value) {if constexpr (std::is_same<T, std::string>::value) {std::cout << "String: " << value << std::endl;} else if constexpr (std::is_arithmetic<T>::value) {std::cout << "Arithmetic result: " << (value + value) << std::endl;} else {std::cout << "Other type" << std::endl;}
}int main() {process(1); // Processing integral type: 1process(3.14); // Processing floating point type: 3.14process("hello"); // Processing other typesprintPositiveOrNegative(-1); // NegativeprintPositiveOrNegative(0); // Positive or zeroprintPositiveOrNegative(1u); // Always positive or zeroconstexpr int result = factorial<5>();std::cout << "5! = " << result << std::endl; // 5! = 120using namespace std::string_literals; // 使用 string 字面量, 下一行才能使用// "hello"sprintOrCompute("hello"s); // String: helloprintOrCompute("world"); // Other typeprintOrCompute(1); // Arithmetic result: 2printOrCompute(3.14); // Arithmetic result: 6.28return 0;
}
consteval
consteval
是C++20中的一个新关键字,用于定义只能在编译时计算的函数。consteval
函数必须在编译时就能计算出结果,否则会导致编译错误。
#include <utility>// fib 函数用来求第n个Fibonacci数
consteval int fib(int n) {int a = 0, b = 1;for (int i = 0; i < n; i++) {a = std::exchange(b, a + b);}return a;
}const int K = 10;
constexpr int cx = fib(0); // OK
const int c1 = fib(0); // OK
int g1 = fib(10); // OK
int g2 = fib(K); // OK, K 是常量
int g3 = fib(cx); // OK, cx 是常量表达式int g5 = fib(g1); // Error, g1 不是常量
consteval int g6 = 1; // Error, consteval 不能修饰变量int main() { return fib(0); }
constinit
constinit 是 C++20 中引入的一个新关键字,用于确保变量在程序启动前完成初始化。这对于需要在编译时就确定其值的全局或静态变量特别有用。
constinit
关键字的提出是为了解决如下的问题:
-
确定性初始化:确保全局或静态变量在程序启动前完成初始化,提供了一种明确的方式来声明这些变量的初始化时机,从而避免了静态初始化顺序问题(SIOF)。
-
性能优化:与动态初始化相比,
constinit
确保了变量的初始化可以在编译时进行,减少了运行时的开销。这对于性能敏感的应用程序来说是一个重要的优势。 -
编译时检查:
constinit
要求变量必须在编译时就能初始化。这种强制性的编译时检查可以避免运行时错误和不确定的行为,提高了代码的安全性和可靠性。 -
与
constexpr
和consteval
的互补:constinit
与C++20中的其他两个关键字constexpr
和consteval
一起,提供了一套完整的工具,用于控制变量和函数的编译时行为。constexpr
允许在编译时或运行时计算,consteval
强制函数必须在编译时计算,而constinit
确保变量在程序启动前初始化。
consinit
的使用要求:
- 变量必须是全局变量或静态变量, 但不一定具有常量属性.
- 变量必须是用常量初始化的(常量字面值,
constexpr
或consteval
)。
/* 全局变量 */
constinit int global_var = 1; // OK
constinit static int global_max = 100; // OK
constinit const int global_min = 10; // OK
constinit int init_var = global_var; // Error, 需要使用常量初始化int main() {constinit static int static_var = 9; // OKconstinit int local_var = 8; // Error, local_var 不是 staticreturn 0;
}