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
函数和lambdaconstexpr
修饰函数表示该函数在编译时就能计算出结果.
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); // OK
return abs(0);
}
if constexpr
编译时分支if constexpr
引入的主要原因是为了提高C++模板编程的能力和效率,具体包括:
编译时分支决策:if constexpr
允许在编译时根据模板参数或其他编译时可知的条件进行条件分支,这意味着可以在编译时决定哪些代码会被编译进最终的程序中。这对于模板元编程尤其重要,因为它允许基于类型特性进行条件编译,从而避免了运行时的分支判断,提高了程序的效率。
简化模板代码:在引入if constexpr
之前,实现基于类型的条件编译通常需要使用模板特化或SFINAE(替换失败不是错误)技术,这些技术不仅代码复杂,而且对于初学者来说难以理解。if constexpr
简化了这一过程,使得基于类型条件的代码分支更加直观和易于编写。
避免无效代码实例化:在模板编程中,某些代码路径可能对于特定的模板参数是无效的。使用if constexpr
可以确保只有有效的代码路径会被实例化,从而避免编译错误。
优化性能:由于if constexpr
在编译时就决定了代码的执行路径,它可以帮助编译器生成更优化的代码。对于不满足条件的分支,由于它们根本不会被编译,因此可以减少最终程序的大小,并提高运行时性能。
增强代码可读性和维护性:if constexpr
使得条件编译的意图更加明显,提高了代码的可读性。同时,由于减少了模板特化和SFINAE的需要,也使得代码更容易维护。
#include
#include
#include
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: 1
process(3.14); // Processing floating point type: 3.14
process("hello"); // Processing other types
printPositiveOrNegative(-1); // Negative
printPositiveOrNegative(0); // Positive or zero
printPositiveOrNegative(1u); // Always positive or zero
constexpr int result = factorial<5>();
std::cout << "5! = " << result << std::endl; // 5! = 120
using namespace std::string_literals; // 使用 string 字面量, 下一行才能使用
// "hello"s
printOrCompute("hello"s); // String: hello
printOrCompute("world"); // Other type
printOrCompute(1); // Arithmetic result: 2
printOrCompute(3.14); // Arithmetic result: 6.28
return 0;
}
consteval
consteval
是C++20中的一个新关键字,用于定义只能在编译时计算的函数。consteval
函数必须在编译时就能计算出结果,否则会导致编译错误。
#include
// 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; // OK
constinit int local_var = 8; // Error, local_var 不是 static
return 0;
}