常量表達式
定義一個可在編譯時求值的表示式。
這種表示式可以用作非型別模板實參、陣列大小,以及其他要求常量表達式的語境,例如:
int n = 1; std::array<int, n> a1; // Error: “n” is not a constant expression const int cn = 2; std::array<int, cn> a2; // OK: “cn” is a constant expression
目錄 |
[編輯] 定義
屬於下列常量表達式類別之一的表示式是常量表達式。
|
(C++11 前) | ||
下列表達式統稱為常量表達式
|
(C++11 起) (C++14 前) | ||
下列實體是常量表達式的允許結果 常量表達式是引用一個作為常量表達式允許結果的實體的泛左值核心常量表達式,或其值滿足下列約束的純右值核心常量表達式
|
(C++14 起) (直到 C++26) | ||
(C++26 起) |
在確定表示式是否為常量表達式時,假定不執行複製消除。
C++98 中常量表達式的定義完全位於摺疊框內。以下描述適用於 C++11 及更高版本的 C++。
[編輯] 字面型別
下列型別統稱為字面型別 (literal types)
- 它有一個平凡解構函式(C++20 前)constexpr 解構函式(C++20 起)。
- 其所有非靜態、非變體資料成員和基類都是非 volatile 的字面型別。
- 它是下列型別之一
(C++17 起) |
只有字面型別的物件才能在常量表達式內建立。
[編輯] 核心常量表達式
核心常量表達式是其求值不會對下列任何語言構造進行求值的任何表示式
語言構造 | 版本 | 提案 |
---|---|---|
this 指標,除非在作為表示式一部分求值的 constexpr 函式中,或出現在隱式或顯式類成員訪問表示式中 |
N2235 | |
一個經過具有靜態或執行緒儲存期且不可在常量表達式中使用的塊變數宣告的控制流 | (C++23 起) | P2242R3 |
本節不完整 原因:將下面原始 HTML 有序列表中的內容轉移到上面的維基表格中,同時新增引入相應項到標準的提案/CWG 問題。小示例不保留,它們可以組合成此頁面底部的大示例。 |
- 呼叫未宣告為 constexpr 的函式(或建構函式)的函式呼叫表示式
constexpr int n = std::numeric_limits<int>::max(); // OK: max() is constexpr constexpr int m = std::time(nullptr); // Error: std::time() is not constexpr
- 對已宣告但未定義的 constexpr 函式的函式呼叫
- 對 constexpr 函式/建構函式模板例項化的函式呼叫,而該例項化不滿足 constexpr 函式/建構函式的要求。
- 對一個 constexpr 虛擬函式的函式呼叫,該呼叫作用於其動態型別為 constexpr 未知的物件上
- 將超出實現定義限制的表示式
- 其求值導致任何形式的核心語言未定義或謬誤(C++26 起)行為的表示式,除了由標準屬性引入的任何潛在未定義行為。
constexpr double d1 = 2.0 / 1.0; // OK constexpr double d2 = 2.0 / 0.0; // Error: not defined constexpr int n = std::numeric_limits<int>::max() + 1; // Error: overflow int x, y, z[30]; constexpr auto e1 = &y - &x; // Error: undefined constexpr auto e2 = &z[20] - &z[3]; // OK constexpr std::bitset<2> a; constexpr bool b = a[2]; // UB, but unspecified if detected
- (C++17 前) lambda 表示式
- 左值到右值隱式轉換,除非應用於……
- 型別為(可能 cv 限定的)std::nullptr_t 的泛左值
- 指定可在常量表達式中使用的物件的非 volatile 字面型別泛左值
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize is a constant expression // because tabsize is usable in constant expressions // because it has const-qualified integral type, and // its initializer is a constant initializer std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // Error: sz is not a constant expression // because sz is not usable in constant expressions // because its initializer was not a constant initializer }
- 引用其生存期在此表示式求值內開始的非 volatile 物件的非 volatile 字面型別泛左值
- 應用於聯合體的非活躍成員或其子物件的左值到右值隱式轉換或修改(即使它與活躍成員共享一個共同的初始序列)
- 對其值不確定的物件的左值到右值隱式轉換
- 對聯合體的隱式複製/移動建構函式/賦值的呼叫,該聯合體的活躍成員是可變的(如果有),且其生存期在此表示式求值外開始
- (C++20 前) 將改變聯合體活躍成員的賦值表示式
- 從指向 void 的指標到指向物件型別的指標
T*
的轉換,除非該指標持有空指標值或指向其型別與T
相似的物件(C++26 起) -
dynamic_cast
,其運算元是引用動態型別為 constexpr 未知的物件的泛左值(C++20 起) -
reinterpret_cast
- (C++20 前) 偽解構函式呼叫
- (C++14 前) 自增或自減運算子
-
(C++14 起) 對物件的修改,除非該物件具有非 volatile 的字面型別且其生存期在此表示式的求值內開始
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // Error: incr(k) is not a core constant // expression because lifetime of k // began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK: x is not required to be initialized // with a core constant expression return x; } constexpr int y = h(1); // OK: initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside the expression h(1)
- (C++20 起) 對生存期未在此表示式求值內開始的物件的解構函式呼叫或偽解構函式呼叫
- 應用於多型型別泛左值的
typeid
表示式,且該泛左值引用動態型別為 constexpr 未知的物件(C++20 起) - new 表示式,除非滿足以下條件之一:(C++20 起)
- 所選擇的分配函式是可替換的全域性分配函式,且分配的儲存在此表示式的求值內被釋放。
(C++20 起) - 所選擇的分配函式是分配型別為
T
的非分配形式,且佈局實參滿足所有下列條件
- 它指向
- 如果
T
不是陣列型別,則指向一個型別與T
相似的物件,或者 - 如果
T
是陣列型別,則指向一個型別與T
相似的物件的首元素。
- 如果
- 它指向的儲存,其時長在此表示式求值內開始。
(C++26 起) - delete 表示式,除非它釋放的是在此表示式求值內分配的儲存區域(C++20 起)
- (C++20 起) 協程:await 表示式或 yield 表示式
- (C++20 起) 當結果未指定時的三路比較
- 結果未指定的相等或關係運算符
- (C++14 前) 賦值或複合賦值運算子
- (C++26 前) throw 表示式
- (C++26 起) 異常物件的構造,除非該異常物件及其透過呼叫 std::current_exception 或 std::rethrow_exception 建立的所有隱式副本都在此表示式求值內被銷燬
constexpr void check(int i) { if (i < 0) throw i; } constexpr bool is_ok(int i) { try { check(i); } catch (...) { return false; } return true; } constexpr bool always_throw() { throw 12; return true; } static_assert(is_ok(5)); // OK static_assert(!is_ok(-1)); // OK since C++26 static_assert(always_throw()); // Error: uncaught exception
- asm-宣告
- 對 va_arg 宏的呼叫
goto
語句- 會丟擲異常的
dynamic_cast
或typeid
表示式或 new 表示式(C++26 起),且異常型別的定義不可達(C++26 起) - 在 lambda 表示式內部,對 this 或對該 lambda 外部定義的變數的引用,如果該引用是 odr-使用
void g() { const int n = 0; constexpr int j = *&n; // OK: outside of a lambda-expression [=] { constexpr int i = n; // OK: 'n' is not odr-used and not captured here. constexpr int j = *&n; // Ill-formed: '&n' would be an odr-use of 'n'. }; }
注意,如果 ODR-使用發生在對閉包的函式呼叫中,它不引用 this 或外圍變數,因為它訪問的是閉包的資料成員
// OK: 'v' & 'm' are odr-used but do not occur in a constant-expression // within the nested lambda auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; // OK to have captures to automatic objects created during constant expression evaluation. static_assert(bind(monad(2))(monad)() == monad(2)());
(C++17 起)
[編輯] 額外要求
即使表示式 E 不對上述任何內容進行求值,如果對 E 的求值會導致執行時未定義行為,則 E 是否為核心常量表達式是實現定義的。
即使表示式 E 不對上述任何內容進行求值,如果對 E 的求值會求值下列任何一項,則 E 是否為核心常量表達式是未指定的
為了確定表示式是否為核心常量表達式,如果 T
是字面型別,則忽略對 std::allocator<T> 成員函式體的求值。
為了確定表示式是否為核心常量表達式,對聯合體的平凡複製/移動建構函式或複製/移動賦值運算子的呼叫,被認為是在複製/移動聯合體的活躍成員(如果有)。
為了確定表示式是否為核心常量表達式,對命名結構化繫結 bd 的識別符號表示式的求值具有以下語義
|
(C++26 起) |
在將表示式作為核心常量表達式求值期間,所有引用其生存期在此表示式求值外開始的物件或引用的識別符號表示式和 *this 的使用,都被視為引用該物件或引用的特定例項,該例項及其所有子物件(包括所有聯合體成員)的生存期包含整個常量求值過程。
- 對於這樣一個不可在常量表達式中使用的(C++20 起)物件,該物件的動態型別是 constexpr-未知的。
- 對於這樣一個不可在常量表達式中使用的(C++20 起)引用,該引用被視為繫結到一個未指定的被引用型別的物件,該物件的生存期及其所有子物件的生存期包含整個常量求值過程,並且其動態型別是 constexpr-未知的。
[編輯] 整型常量表達式
整型常量表達式 (integral constant expression) 是一個整型或無作用域列舉型別的表示式,它被隱式轉換為一個純右值,其中轉換後的表示式是核心常量表達式。
如果一個類型別的表示式被用在需要整型常量表達式的地方,該表示式將被按語境隱式轉換為整型或無作用域列舉型別。
[編輯] 經轉換的常量表達式
型別為 T
的經轉換的常量表達式 (converted constant expression) 是一個隱式轉換為型別 T
的表示式,其中轉換後的表示式是常量表達式,並且隱式轉換序列只包含
(C++17 起) |
以下語境需要經轉換的常量表達式
(C++14 起) | |
(C++26 起) |
型別為 bool 的語境轉換的常量表達式(contextually converted constant expression of type bool)是一個表示式,它語境轉換為 bool,其中被轉換的表示式是一個常量表達式,且轉換序列只包含上述轉換。
下列語境要求一個型別為 bool 的語境轉換的常量表達式
(直至 C++23) | |
(C++17 起) (直至 C++23) | |
(C++20 起) |
構成實體一個物件 obj 的構成值(constituent values)定義如下 一個物件 obj 的構成引用(constituent references)包括以下引用
一個變數 var 的構成值和構成引用定義如下
對於變數 var 的任何構成引用 ref,如果 ref 繫結到一個臨時物件或其子物件,且該臨時物件的生存期被延長至 ref 的生存期,那麼該臨時物件的構成值和引用也遞迴地成為 var 的構成值和引用。 可用 constexpr 表示的實體具有靜態儲存期的物件在程式的任何點都是可用 constexpr 引用(constexpr-referenceable)的。 具有自動儲存期的物件 obj 從點 一個物件或引用 x 在點
|
(C++26 起) | ||||||||
常量初始化的實體
可用於常量表達式一個變數是潛在常量(potentially-constant)的,如果它是一個 constexpr 變數,或者它具有引用型別或非 volatile 的 const 限定的整型或列舉型別。 一個經過常量初始化的潛在常量變數 var 在點
明顯常量求值的表示式下列表達式(包括到目標型別的轉換)是明顯常量求值(manifestly constant-evaluated)的
一個求值是否發生在明顯常量求值的語境中,可以透過 std::is_constant_evaluated 和 |
(C++20 起) |
[編輯] 常量求值所需的函式和變數
下列表達式或轉換是潛在常量求值(potentially constant evaluated)的
- 明顯常量求值的表示式
- 潛在求值的表示式
- 花括號環繞的初始值列表的直接子表示式(可能需要常量求值來確定轉換是否為窄化轉換)
- 出現在模板化實體中的取地址表示式(可能需要常量求值來確定這樣的表示式是否是值依賴的)
- 上述之一的子表示式,且該子表示式本身不是巢狀的未求值運算元的子表示式
一個函式是常量求值所需(needed for constant evaluation)的,如果它是一個 constexpr 函式且被一個潛在常量求值的表示式指名。
一個變數是常量求值所需的,如果它是一個 constexpr 變數,或者具有非 volatile 的 const 限定整型型別或引用型別,並且指代它的識別符號表示式是潛在常量求值的。
如果一個函式或變數(C++14 起)是常量求值所需的,則會觸發一個預置函式的定義和函式模板特化或變數模板特化(C++14 起)的例項化。
[編輯] 常量子表示式
常量子表示式(constant subexpression)是這樣一個表示式,其作為表示式 e 的子表示式的求值不會阻止 e 成為核心常量表達式,其中 e 不是下列任何表示式
(C++20 起) |
[編輯] 注意
功能測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_constexpr_in_decltype |
201711L |
(C++20) (DR11) |
當常量求值所需時生成函式和變數的定義 |
__cpp_constexpr_dynamic_alloc |
201907L |
(C++20) | constexpr 函式中的動態儲存持續時間操作 |
__cpp_constexpr |
202306L |
(C++26) | 從 void* 進行 constexpr 轉換:邁向 constexpr 型別擦除 |
202406L |
(C++26) | constexpr 就地 new 和 new[] | |
__cpp_constexpr_exceptions |
202411L |
(C++26) | constexpr 異常 |
[編輯] 示例
本節不完整 原因:無示例 |
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 94 | C++98 | 算術常量表達式不能 涉及變數和靜態資料成員 |
推導指引可以有尾隨的requires子句 |
CWG 366 | C++98 | 涉及字串字面量的表示式 可以是整型常量表達式 |
它們沒有 |
CWG 457 | C++98 | 涉及 volatile 變數的表示式 可以是整型常量表達式 |
它們沒有 |
CWG 1293 | C++11 | 不清楚字串字面量是否 可用於常量表達式 |
它們是可用的 |
CWG 1311 | C++11 | volatile 左值可用於常量表達式 | 已禁止 |
CWG 1312 | C++11 | reinterpret_cast 在常量表達式中被禁止, 但與 void* 之間的轉換可以達到同樣的效果 |
禁止從型別 cv void* 到 指向物件型別的指標的轉換 |
CWG 1313 | C++11 | 未定義行為是允許的; 所有指標減法都被禁止 |
未定義行為被禁止;同一陣列 內的指標減法是允許的 |
CWG 1405 | C++11 | 對於可用於常量表達式的物件, 其可變子物件也是可用的 |
它們是不可用的 |
CWG 1454 | C++11 | 透過引用將常量傳遞給 constexpr 函式是不允許的 |
允許 |
CWG 1455 | C++11 | 轉換的常量表達式只能是純右值 | 可以是左值 |
CWG 1456 | C++11 | 地址常量表達式不能 指代陣列末尾之後一個元素的地址 |
允許 |
CWG 1535 | C++11 | 一個 typeid 表示式,其運算元是 多型類型別,不是核心常量 表示式,即使不涉及執行時檢查 |
運算元的約束 被限制為 多型類型別的左值 |
CWG 1581 | C++11 | 常量求值所需的函式 不要求被定義或例項化 |
需要 |
CWG 1613 | C++11 | 核心常量表達式可以求值任何 lambda 表示式內被 ODR 使用的引用 |
某些引用 不能被求值 |
CWG 1694 | C++11 | 將臨時物件的值繫結到靜態儲存期 的引用是一個常量表達式 |
它不是 常量表達式 |
CWG 1872 | C++11 | 核心常量表達式可以呼叫不滿足 constexpr 函式要求的 constexpr 函式模板例項化 |
這樣的例項化 不能被呼叫 |
CWG 1952 | C++11 | 標準庫的未定義行為 被要求診斷 |
未指明是否 對它們進行診斷 |
CWG 2022 | C++98 | 常量表達式的判定可能 取決於是否執行復制消除 |
假定複製消除 總是被執行 |
CWG 2126 | C++11 | 常量初始化的、生存期延長的 const 限定字面型別的臨時物件不可用於常量表達式 |
可用 |
CWG 2129 | C++11 | 整數字面量不是常量表達式 | 它們是 |
CWG 2167 | C++11 | 求值區域性的非成員引用 使得該求值非 constexpr |
非成員 引用是允許的 |
CWG 2278 | C++98 | CWG 問題 2022 的解決方案不可實現 | 假定複製消除 從不執行 |
CWG 2299 | C++14 | 不清楚 <cstdarg> 中的宏是否 可以在常量求值中使用 |
va_arg 被禁止,va_start 未指明 |
CWG 2400 | C++11 | 在一個不可用於常量表達式且其生存期始於 包含該呼叫的表示式之外的物件上呼叫一個 constexpr 虛擬函式, 可能是一個常量表達式 |
它不是 常量表達式 |
CWG 2490 | C++20 | (偽)解構函式呼叫在常量求值中 缺乏限制 |
添加了限制 |
CWG 2552 | C++23 | 在求值核心常量表達式時,控制流 不能透過非塊變數的宣告 |
它可以 |
CWG 2558 | C++11 | 未定值可以是常量表達式 | 不是常量表達式 |
CWG 2647 | C++20 | volatile 限定型別的變數可以是潛在常量的 | 它們沒有 |
CWG 2763 | C++11 | 不要求在常量求值期間 檢測對 [[noreturn]] 的違反 |
需要 |
CWG 2851 | C++11 | 轉換的常量表達式 不允許浮點轉換 |
允許非窄化 浮點轉換 |
CWG 2907 | C++11 | 核心常量表達式不能將 左值到右值轉換應用於 std::nullptr_t 左值 |
可以應用此類轉換 轉換 |
CWG 2909 | C++20 | 一個沒有初始值設定項的變數只能在 其預設初始化導致 執行了某些初始化時才能被常量初始化 |
僅當其型別是 可 const 預設 初始化時才能被常量初始化 |
CWG 2924 | C++11 C++23 |
未指明一個違反 [[noreturn]] (C++11) 或[[assume]] (C++23) 約束的表示式是否是核心常量表達式 |
它是 實現定義 |
P2280R4 | C++11 | 求值一個包含識別符號表示式 或 *this 的表示式,該表示式引用一個生存期 始於此求值之外的物件或引用,不是一個常量表達式 |
它可以是 常量表達式 |
[編輯] 參閱
constexpr 說明符(C++11) |
指定變數或函式的值可以在編譯時計算 |
(C++11)(C++17 中已棄用)(C++20 中已移除) |
檢查型別是否為字面型別 (類模板) |
C 文件中有關常量表達式的內容
|