constexpr
constexpr(const expression常量表達式)
只有像1,'a',2.5f之類的才是真正的常量。儲存在內存中的數據都應當叫做“變量”。我們既希望定義一個安全的值,又不希望這個數據成為「變量」來占用空間。關鍵字 constexpr 是在 C++11 中引入的,并在 C++14 中進行了改進。 它表示「常量表達式」。 與 const 一樣,它可以應用于變量:當任何代碼嘗試修改值時,都會引發編譯器錯誤。 與 const 不同,constexpr 還可以應用于函數和類構造函數。 constexpr 指示該值或返回值是常量,并且盡可能在編譯時計算。
constexpr變量
const 和 constexpr 變量之間的主要區別是, const 變量的初始化可能推遲到運行時。 constexpr 變量必須在 編譯期初始化 。所有的 constexpr 變量都是 const。 。
- 如果一個變量具有文本類型且已經初始化,則可以用
constexpr聲明,如果初始化是由構造函數執行的,那么必須將構造函數聲明為constexpr。 - 當滿足以下兩個條件時,可以將引用聲明為
constexpr:- 引用的對象由常量表達式初始化
- 在初始化期間所調用的任何隱式轉換也均是常數表達式。
constexpr變量或函數的所有聲明都必須具有constexpr說明符。
struct Test {
Test() : a(10) {}
const int a; // ok
};
constexpr float x = 42.0;
constexpr float y{108};
constexpr float z = exp(5, 3);
constexpr int i; // Error! Not initialized
int j = 0;
constexpr int k = j + 1; //Error! j not a constant expression
變量:
constexpr int x = 42; //ok,文本類型+常量表達式初始化
constexpr float y = sqrt(9.0); // ok,如果sqrt是constexpr函數
class Point {
public:
constexpr Point(int a, int b) : x(a), y(b) {} // constexpr構造函數
int x, y;
};
constexpr Point p(1, 2); // 合法:構造函數是constexpr
引用:
constexpr int a = 10;
constexpr const int& ref = a; // 合法:a是常量表達式
constexpr double b = 3.14;
constexpr int c = static_cast<int>(b); // 合法:顯式轉換是編譯期常量
constexpr函數
constexpr 函數主要適用于編譯期返回 constexpr 變量。 使用在需要編譯時的返回值來初始化 constexpr 變量時,或者用于提供非類型模板的自變量。 如果參數是 constexpr 值時, constexpr 函數會生成編譯時常量。 如果是不同的值時,或者編譯期時不需要其去調用執行這個函數,它將與普通函數一樣,在運行時生成一個值。 (此行為使你無需編寫同一函數的 constexpr 和非 constexpr 版本。)
constexpr 函數或構造函數是隱式 inline。
以下規則適用于「constexpr函數」:
constexpr函數必須只接受并返回文本類型,即編譯期可確定的值。constexpr函數可以是遞歸的。- 在 C++20 之前,
constexpr函數不能是虛,并且當類具有任何虛擬基類時,不能將構造函數定義為constexpr。 在 C++20 及更高版本中,constexpr函數可以是虛函數。 - 主體可以定義為
= default或= delete。 - C++11僅支持
return,C++14之后允許局部變量、循環、條件語句,但不能包含goto語句或try塊、動態內存分配(new、delete)以及修改全局變量。 - 非
constexpr模板的顯式特化可聲明為constexpr:- 即使原模板未標記
constexpr,其顯式特化版本可單獨聲明為constexpr,以滿足特定類型的編譯期計算需求。
- 即使原模板未標記
template <typename T>
T add(T a, T b) { return a + b; } // 非constexpr模板
template <>
constexpr int add<int>(int a, int b) { return a + b; } // 顯式特化為constexpr
constexpr模板的顯式特化不必是constexpr:- 若原模板為
constexpr,其顯式特化可選擇性省略constexpr,退化為運行時函數。 - 用途:針對某些類型需運行時處理時(如涉及I/O或動態內存),保留靈活性。
- 若原模板為
template <typename T>
constexpr T square(T x) { return x * x; } // constexpr模板
template <>
double square<double>(double x) { return x * x; } // 顯式特化非constexpr
constexpr 表達式也可能是變成變量
雖然 constexpr 已經是常量表達式了,但是用 constexpr 修飾變量的時候,它仍然是“定義變量”的語法,因此C++希望它能夠兼容只讀變量的情況。
當且僅當一種情況下, constexpr 定義的變量會真的成為變量,那就是這個變量被取址的時候:
void Demo() {
constexpr int a = 5;
const int *p = &a; // 會讓a退化為const int類型
}
道理也很簡單,因為只有變量才能取址。上面例子中,由于對 a 進行了取地址操作,因此, a 不得不真正成為一個變量,也就是變為 const int 類型。
那另一個問題就出現了,如果說,我對一個常量表達式既取了地址,又用到編譯期語法中了怎么辦?
template <int N>
struct Test {};
void Demo() {
constexpr int a = 5;
Test<a> t; // 用做常量
const int *p = &a; // 用做變量
}
沒關系,編譯器會讓它在編譯期視為常量去給那些編譯期語法(比如模板實例化)使用,之后,再把它用作變量寫到內存中。
換句話說,在編譯期,這里的a相當于一個宏,所有的編譯期語法會用5替換a,Test<a> 就變成了 Test<5>。之后,又會讓 a 成為一個只讀變量寫到內存中,也就變成了 const int a = 5; 那么 const int *p = &a; 自然就是合法的了。
if constexpr
C++17引入的編譯時條件判斷工具,在編譯時進行判斷,編譯條件必須是常量表達式,針對 false 的情況,不進行編譯。
#include <iostream>
#include <type_traits>
template<typename T>
void handleValue(const T& value) {
if constexpr (std::is_integral_v<T>) {
// 此分支僅在 T 為整型時編譯
std::cout << value << " 是整型,進行整型處理。" << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
// 此分支僅在 T 為浮點型時編譯
std::cout << value << " 是浮點型,進行浮點型處理。" << std::endl;
} else {
// 此分支用于其他類型
std::cout << value << " 是其他類型。" << std::endl;
}
}
int main() {
handleValue(42); // 輸出:42 是整型,進行整型處理。
handleValue(3.14); // 輸出:3.14 是浮點型,進行浮點型處理。
handleValue("Hello"); // 輸出:Hello 是其他類型。
return 0;
}
constexpr lambda
constexpr lambda 是 C++17 引入的特性,允許 lambda 表達式在編譯時進行求值,從而將編譯時計算的能力與 lambda 的簡潔語法相結合。
一個 constexpr lambda 可以通過在參數列表后顯式添加 constexpr 關鍵字來聲明:
auto square = [](int n) constexpr { return n * n; };
如果 lambda 體的操作都滿足 constexpr 函數的要求(例如,只包含確定性操作,不進行 I/O 或調用非 constexpr 函數),那么它也可以被隱式地視為 constexpr ,無需顯式聲明
// 這是一個隱式的 constexpr lambda
auto add = [](int a, int b) { return a + b; };
constexpr int result = add(10, 20); // 編譯時計算

浙公網安備 33010602011771號