常數運算式
定義一個可以在編譯時期求值的運算式。
此類運算式可用作非型別模板參數、陣列大小,以及其他需要常數運算式的場景,例如:
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 前) | ||
|
下列實體是常數運算式的許可結果
常數運算式或是指涉一個作為常數運算式許可結果之實體的廣義左值 (glvalue) 核心常數運算式,或是其值滿足下列約束的純右值核心常數運算式
|
(C++14 起) (直到 C++26) | ||
| (C++26 起) |
在判定運算式是否為常數運算式時,假設不執行複寫消除 (copy elision)。
C++98 的常數運算式定義完全包含在摺疊框內。以下描述適用於 C++11 及更高版本的 C++。
[編輯] 常值型別 (Literal type)
下列型別統稱為常值型別
- 具有 顯微解構子(至 C++20)constexpr 解構子(自 C++20 起)。
- 其所有非靜態且非變體 (non-variant) 資料成員及基底類別皆為非 volatile 常值型別。
- 為下列型別之一
| (自 C++17 起) |
- 滿足下列條件之一的聚合 (aggregate) 聯集型別
- 沒有變體成員。
- 具有至少一個非 volatile 常值型別的變體成員。
- 非聯集聚合型別,且其每個匿名聯集成員皆滿足下列條件之一
- 沒有變體成員。
- 具有至少一個非 volatile 常值型別的變體成員。
- 具有至少一個不是複寫或移動構造函式的 constexpr 構造函式(模板)的型別
只有常值型別的物件可以在常數運算式中建立。
[編輯] 核心常數運算式
核心常數運算式是求值過程不會求值下列任何語言結構的任何運算式
| 語言結構 | 版本 | 提案論文 (Papers) |
|---|---|---|
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-unknown
- 會超出實作定義限制的運算式
- 其求值導致任何形式的核心語言 未定義 或錯誤(自 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 常值型別廣義左值
- 對 聯集 的非活動成員或其子物件套用的左值轉右值隱式轉換或修改(即使它與活動成員共享共同的初始序列)
- 對 值為不確定 的物件套用左值轉右值隱式轉換
- 在生命週期始於此運算式求值之外的情況下,對活動成員為可變 (mutable) 的聯集(若有)呼叫隱式複寫/移動構造函式/賦值運算子
- (至 C++20) 會改變聯集活動成員的賦值運算式
- 從 指向 void 的指標 到物件指標型別
T*的轉換,除非指標保存空指標值或指向型別與T相似 的物件(自 C++26 起) -
dynamic_cast且運算元是個指涉動態型別為 constexpr-unknown 物件的廣義左值(自 C++20 起) -
reinterpret_cast - (至 C++20) 偽解構子 (pseudo-destructor) 呼叫
- (至 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-unknown 的物件(自 C++20 起) - new 運算式,除非滿足下列條件之一:(自 C++20 起)
- 所選的 配置函式 是可替換的全域配置函式,且所配置的儲存空間在此運算式求值過程中被歸還。
(自 C++20 起) - 所選的配置函式是非配置形式 (non-allocating form) 且配置型別為
T,且配置引數滿足下列所有條件
- 它指向:
- 一個型別與
T相似的物件(若T不是陣列型別),或 - 一個型別與
T相似之物件的第一個元素(若T是陣列型別)。
- 一個型別與
- 它指向一個生命週期始於此運算式求值過程中的儲存空間。
(C++26 起) - delete 運算式,除非它歸還在此運算式求值過程中配置的儲存區域(自 C++20 起)
- (自 C++20 起) 協程 (Coroutines):await-運算式或 yield-運算式
- (自 C++20 起) 當結果未指定時的 三路比較 (three-way comparison)
- 結果未指定的相等或關聯運算子
- (至 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-use
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-use 發生在對閉包的函式呼叫中,它不指涉 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-unknown。
- 對於此類並非可用於常數運算式(自 C++20 起)的參考,該參考被視為綁定到一個所參考型別的未指定物件,該物件其生命週期及所有子物件的生命週期皆涵蓋整個常數求值過程,且其動態型別為 constexpr-unknown。
[編輯] 整數常數運算式
整數常數運算式是一個隱式轉換為純右值的整數或無作用域列舉型別的運算式,其中轉換後的運算式是個核心常數運算式。
若在需要整數常數運算式的地方使用了類別型別運算式,則該運算式會按語境隱式轉換為整數或無作用域列舉型別。
[編輯] 經轉換的常數運算式
型別 T 的經轉換常數運算式是個隱式轉換為型別 T 的運算式,其中轉換後的運算式是個常數運算式,且隱式轉換序列僅包含
| (自 C++17 起) |
下列語境需要經轉換的常數運算式
| (C++14 起) | |
| (C++26 起) |
型別為 bool 的語境轉換常數運算式是個按語境轉換為 bool 的運算式,其中轉換後的運算式是常數運算式,且轉換序列僅包含上述轉換。
下列語境需要型別為 bool 的語境轉換常數運算式
| (至 C++23 止) | |
| (自 C++17 起) (至 C++23 止) | |
| (自 C++20 起) |
構成實體 (Constituent entities)物件 obj 的構成值 (constituent values) 定義如下 物件 obj 的構成參考 (constituent references) 包括下列參考
變數 var 的構成值與構成參考定義如下
對於變數 var 的任何構成參考 ref,若 ref 綁定到暫時物件(或其子物件)且該暫時物件的生命週期延長至 ref 的生命週期,則該暫時物件的構成值與參考也遞迴地成為 var 的構成值與參考。 可經由 Constexpr 表示的實體具有靜態儲存期的物件在程式中任何位置皆為 constexpr-referenceable。 具有自動儲存期的物件 obj 在位置 物件或參考 x 在位置
|
(C++26 起) | ||||||||
常數初始化的實體
可用於常數運算式變數是潛在常數 (potentially-constant),若它是 constexpr 變數,或者它具有參考型別、或具有非 volatile const 限定的整數或列舉型別。 常數初始化的潛在常數變數 var 在位置
顯明常數求值的運算式 (Manifestly constant-evaluated expressions)下列運算式(包括向目標型別的轉換)是顯明常數求值的 (manifestly constant-evaluated)
求值是否發生在顯明常數求值的語境中,可藉由 std::is_constant_evaluated 及 |
(自 C++20 起) |
[編輯] 常數求值所需的函式與變數
下列運算式或轉換是潛在常數求值的 (potentially constant evaluated)
- 顯明常數求值的運算式
- 潛在求值的 (potentially-evaluated) 運算式
- 大括號包圍的初始化列表的直接子運算式(可能需要常數求值來判斷轉換是否為窄化轉換)
- 出現在模板化實體內的取位址運算式(可能需要常數求值來判斷此類運算式是否為值相依)
- 上述項目的子運算式,且該子運算式並非巢狀的不求值運算元的子運算式
函式是常數求值所需的,若它是個 constexpr 函式,且被一個潛在常數求值的運算式所命名。
變數是常數求值所需的,若它是個 constexpr 變數,或者它具有非 volatile const 限定的整數型別或參考型別,且表示它的識別字運算式是潛在常數求值的。
若函式或變數(自 C++14 起)是常數求值所需的,則會觸發預設函式的定義與函式模板特化或變數模板特化(自 C++14 起)的具現化。
[編輯] 常數子運算式
常數子運算式是一個運算式,將其作為運算式 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 placement new 與 new[] | |
__cpp_constexpr_exceptions |
202411L |
(C++26) | constexpr 異常 |
[編輯] 範例
| 本節尚不完整 理由:無範例 |
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯應用於之前的 C++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| CWG 94 | C++98 | 算術常數運算式原先不能 涉及變數與靜態資料成員 |
它們可以 |
| 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 | 對於可用於常數運算式的物件, 原先規定其可變 (mutable) 子物件亦可用 |
現改為不可用 |
| 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-used 的參考 |
現規定某些參考 不能被求值 |
| 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 中移除) |
檢查型別是否為字面量 (literal) 型別 (類別模板) |
| C 語言文件 的 常數運算式
| |