隱式轉換
當某個型別 T1 的表達式在不接受該型別但接受其他型別 T2 的語境中使用時,會執行隱式轉換;特別是:
- 當表達式用作函式呼叫的引數時,該函式以
T2作為參數宣告; - 當表達式用作運算元的運算子期望
T2時; - 當初始化型別為
T2的新物件時,包括傳回T2的函式中的return陳述式; - 當表達式在 switch 陳述式中使用時 (
T2為整數型別); - 當表達式在 if 陳述式或迴圈中使用時 (
T2為 bool)。
程式只有在存在一個明確無誤的隱式轉換序列從 T1 到 T2 時才良構(可編譯)。
如果被呼叫的函式或運算子有多個多載,在從 T1 到每個可用的 T2 建立隱式轉換序列後,多載解析規則決定編譯哪個多載。
注意:在算術表達式中,二元運算子的運算元上的隱式轉換的目標型別由一組獨立的規則決定:常用算術轉換。
目錄 |
[編輯] 轉換順序
隱式轉換序列按以下順序組成:
當考慮建構子或使用者定義轉換函式的引數時,只允許一個標準轉換序列(否則使用者定義轉換可能會有效率地鏈接)。當從一種非類別型別轉換為另一種非類別型別時,只允許標準轉換序列。
標準轉換序列按以下順序組成:
- 左值到右值轉換,
- 陣列到指標轉換,以及
- 函式到指標轉換;
|
3) 零個或一個函式指標轉換;
|
(自 C++17 起) |
使用者定義轉換包含零個或一個非顯式單引數轉換建構子或非顯式轉換函式呼叫。
表達式 e 被稱作可隱式轉換為 T2 若且唯若 T2 可以從 e 拷貝初始化,也就是說,對於某個虛擬的暫時物件 t,宣告 T2 t = e; 是良構的(可以編譯)。請注意,這與直接初始化(T2 t(e))不同,後者還會考慮顯式建構子和轉換函式。
[編輯] 語境轉換
|
在以下語境中,預期型別為 bool,如果宣告 bool t(e); 是良構的,則會執行隱式轉換(也就是說,會考慮顯式轉換函式,例如 explicit T::operator bool() const;)。此類表達式 e 被稱作語境式轉換為 bool。
|
(C++11 起) |
在以下語境中,預期語境特定的型別 T,並且類別型別 E 的表達式 e 僅在滿足以下條件時才允許:
|
(C++14 前) |
|
(C++14 起) |
此類表達式 e 被稱作語境式隱式轉換為指定型別 T。請注意,不考慮顯式轉換函式,儘管在語境式轉換為 bool 時它們會被考慮。(C++11 起)
- delete 表達式的引數 (`T` 是任何物件指標型別);
- 整數常數表達式,其中使用了字面量類別 (`T` 是任何整數或無作用域列舉型別,所選的使用者定義轉換函式必須是 constexpr);
switch陳述式的控制表達式 (`T` 是任何整數或列舉型別)。
#include <cassert> template<typename T> class zero_init { T val; public: zero_init() : val(static_cast<T>(0)) {} zero_init(T val) : val(val) {} operator T&() { return val; } operator T() const { return val; } }; int main() { zero_init<int> i; assert(i == 0); i = 7; assert(i == 7); switch (i) {} // error until C++14 (more than one conversion function) // OK since C++14 (both functions convert to the same type int) switch (i + 0) {} // always okay (implicit conversion) }
[編輯] 值轉換
值轉換是改變表達式值類別的轉換。當表達式作為運算元的運算子期望不同值類別的表達式時,就會發生值轉換。
- 每當 gl值 作為運算元出現在要求 pr值 的運算子中時,會應用左值到右值、陣列到指標或函式到指標的標準轉換將表達式轉換為 pr值。
|
(自 C++17 起) |
[編輯] 左值到右值轉換
任何非函式、非陣列型別 T 的 左值(直到 C++11)gl值(C++11 起) 可以隱式轉換為 右值(直到 C++11)pr值(C++11 起)
- 如果
T不是類別型別,右值(直到 C++11)pr值(C++11 起) 的型別是T的 cv 無限定版本。 - 否則,右值(直到 C++11)pr值(C++11 起) 的型別為
T。
如果程式要求從不完整型別進行左值到右值轉換,則該程式為病構。
給定 左值(直到 C++11)gl值(C++11 起) 所引用的物件為 obj
|
(直到 C++11) | ||||
|
(C++11 起) |
此轉換模擬了從記憶體位置讀取值到 CPU 暫存器中的動作。
[編輯] 陣列到指標轉換
型別為「N 個 T 的陣列」或「未知界限 T 的陣列」的左值或右值可以隱式轉換為型別為「指向 T 的指標」的pr值。如果陣列是 pr值,則會發生暫時物件實體化。(C++17 起) 結果指標指向陣列的第一個元素(詳情請參閱陣列到指標衰變)。
[編輯] 函式到指標轉換
函式型別的左值可以隱式轉換為指向該函式的pr值指標。這不適用於非靜態成員函式,因為不存在引用非靜態成員函式的左值。
暫時物件實體化任何完整型別 如果 struct S { int m; }; int i = S().m; // member access expects glvalue as of C++17; // S() prvalue is converted to xvalue 暫時物件實體化發生在以下情況:
請注意,當從相同型別的 pr值 初始化物件時(透過直接初始化或拷貝初始化),不會發生暫時物件實體化:此類物件是直接從初始化器初始化的。這確保了「保證的拷貝省略」。 |
(自 C++17 起) |
[編輯] 整數提升
小型整數型別(例如 char)和無作用域列舉型別的 pr值 可以轉換為較大整數型別(例如 int)的 pr值。特別是,算術運算子不接受小於 int 的型別作為引數,並且如果適用,整數提升會在左值到右值轉換後自動應用。此轉換始終保留值。
本節中的以下隱式轉換被歸類為整數提升。
請注意,對於給定的來源型別,整數提升的目標型別是唯一的,所有其他轉換都不是提升。例如,多載解析會選擇 char -> int(提升)而不是 char -> short(轉換)。
[編輯] 從整數型別提升
型別為 bool 的 pr值 可以轉換為型別為 int 的 pr值,其中 false 變為 0,true 變為 1。
對於除 bool 之外的整數型別 T 的 pr值 val
- 如果 int 可以表示位元欄位的所有值,則 val 可以轉換為型別為 int 的 pr值;
- 否則,如果 unsigned int 可以表示位元欄位的所有值,則 val 可以轉換為 unsigned int;
- 否則,val 可以根據項目 (3) 中指定的規則進行轉換。
- 如果
T是 `char8_t`、(C++20 起)`char16_t`、`char32_t` 或 (C++11 起)`wchar_t`,則 val 可以根據項目 (3) 中指定的規則進行轉換; - 否則,如果
T的整數轉換等級低於 int 的等級
- 如果 int 可以表示
T的所有值,則 val 可以轉換為型別為 int 的 pr值; - 否則,val 可以轉換為型別為 unsigned int 的 pr值。
- 如果 int 可以表示
T 是給定的字元型別之一)指定的情況下,val 可以轉換為以下列表中第一個能表示其底層型別所有值的 pr值:- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
[編輯] 從列舉型別提升
無作用域的列舉型別的 pr值,如果其底層型別未固定,可以轉換為以下列表中第一個能夠容納其整個值範圍的型別的 pr值:
- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
|
無作用域列舉型別的 pr值,如果其底層型別已固定,可以轉換為其底層型別。此外,如果底層型別也受到整數提升的影響,則轉換為提升後的底層型別。對於多載解析的目的,轉換為未提升的底層型別更好。 |
(C++11 起) |
[編輯] 浮點數提升
型別為 float 的 pr值 可以轉換為型別為 double 的 pr值。值不會改變。
此轉換稱為浮點數提升。
[編輯] 數值轉換
與提升不同,數值轉換可能會改變值,並可能損失精度。
[編輯] 整數轉換
整數型別或無作用域列舉型別的 pr值 可以轉換為任何其他整數型別。如果該轉換列在整數提升下,則它是一個提升而非轉換。
- 如果目標型別是無符號型別,則結果值是等於來源值模數 2n
的最小無符號值,其中 n 是用於表示目標型別的位元數。
- 也就是說,根據目標型別是更寬還是更窄,有符號整數會分別進行符號擴展[1]或截斷,而無符號整數會分別進行零擴展或截斷。
- 如果目標型別是有符號型別,如果來源整數可以在目標型別中表示,則值不會改變。否則結果是 實作定義的(直到 C++20)等於來源值模數 2n
的目標型別唯一值,其中 n 是用於表示目標型別的位元數(C++20 起)(請注意,這與有符號整數算術溢位不同,後者是未定義的)。 - 如果來源型別是 bool,值 false 轉換為零,值 true 轉換為目標型別的值一(請注意,如果目標型別是 int,這是一個整數提升,而不是整數轉換)。
- 如果目標型別是 bool,這是一個布林轉換(參見下文)。
[編輯] 浮點數轉換
|
浮點數型別的 pr值 可以轉換為任何其他浮點數型別的 pr值。 |
(至 C++23 止) |
|
浮點數型別的 pr值 可以轉換為任何其他具有更大或相等浮點數轉換等級的浮點數型別的 pr值。 標準浮點數型別的 pr值 可以轉換為任何其他標準浮點數型別的 pr值。
|
(自 C++23 起) |
如果該轉換列在浮點數提升下,則它是一個提升而非轉換。
- 如果來源值可以在目標型別中精確表示,則它不會改變。
- 如果來源值在目標型別的兩個可表示值之間,則結果是這兩個值之一(具體是哪一個由實作定義,但如果支援 IEEE 算術,則預設捨入到最接近的)。
- 否則,其行為是未定義的。
[編輯] 浮點數與整數轉換
浮點數型別的 pr值 可以轉換為任何整數型別的 pr值。分數部分會被截斷,即,分數部分會被丟棄。
- 如果截斷後的值無法放入目標型別,則行為是未定義的(即使目標型別是無符號型別,模數算術也不適用)。
- 如果目標型別是 bool,這是一個布林轉換(參見下文)。
整數或無作用域列舉型別的 pr值 可以轉換為任何浮點數型別的 pr值。如果可能,結果是精確的。
- 如果值可以放入目標型別但不能精確表示,則選擇最接近的較高值還是最接近的較低可表示值是實作定義的,但如果支援 IEEE 算術,則預設捨入到最接近的。
- 如果值無法放入目標型別,則行為是未定義的。
- 如果來源型別是 bool,值 false 轉換為零,值 true 轉換為一。
[編輯] 指標轉換
空指標常數可以轉換為任何指標型別,結果是該型別的空指標值。此類轉換(稱為空指標轉換)允許作為單一轉換轉換為 cv 限定型別,即,它不被視為數值和限定詞轉換的組合。
指向任何(可選 cv 限定的)物件型別 T 的 pr值指標可以轉換為指向(相同 cv 限定的)void 的 pr值指標。結果指標表示與原始指標值相同的記憶體位置。
- 如果原始指標是空指標值,則結果是目標型別的空指標值。
型別為「指向(可能 cv 限定的)Derived 的指標」的 pr值 ptr 可以轉換為型別為「指向(可能 cv 限定的)Base 的指標」的 pr值,其中 Base 是 Derived 的基底類別,且 Derived 是完整類別型別。如果 Base 不可存取或模稜兩可,則程式為病構。
- 如果 ptr 是空指標值,則結果也是空指標值。
- 否則,如果
Base是Derived的虛擬基底類別,且 ptr 不指向型別與Derived相似且在其生命週期內或其建構或解構期間內的物件,則行為是未定義的。 - 否則,結果是指向衍生類別物件的基底類別子物件的指標。
[編輯] 成員指標轉換
空指標常數可以轉換為任何成員指標型別,結果是該型別的空成員指標值。此類轉換(稱為空成員指標轉換)允許作為單一轉換轉換為 cv 限定型別,即,它不被視為數值和限定詞轉換的組合。
型別為「指向 Base 的成員(型別為(可能 cv 限定的)T)」的 pr值 可以轉換為型別為「指向 Derived 的成員(型別為(相同 cv 限定的)T)」的 pr值,其中 Base 是 Derived 的基底類別,且 Derived 是完整類別型別。如果 Base 不可存取、模稜兩可、是 Derived 的虛擬基底,或是 Derived 的某個中間虛擬基底的基底,則程式為病構。
- 如果
Derived不包含原始成員,且不是包含原始成員的類別的基底類別,則行為是未定義的。 - 否則,結果指標可以透過
Derived物件解引用,它將存取該Derived物件的Base基底子物件內的成員。
[編輯] 布林轉換
整數、浮點數、無作用域列舉、指標和成員指標型別的 pr值 可以轉換為型別為 bool 的 pr值。
值零(對於整數、浮點數和無作用域列舉)以及空指標和空成員指標值變為 false。所有其他值變為 true。
|
在直接初始化的語境中,bool 物件可以從型別為 std::nullptr_t 的 pr值 初始化,包括 nullptr。結果值為 false。然而,這不被視為隱式轉換。 |
(C++11 起) |
[編輯] 限定詞轉換
一般而言
- 指向 cv 限定型別
T的指標 pr值 可以轉換為指向更多 cv 限定的相同型別T的 pr值指標(換句話說,可以添加 const 和 volatile 限定)。 - 型別為指向類別
X中 cv 限定型別T成員的 pr值 可以轉換為指向類別X中更多 cv 限定型別T成員的 pr值指標。
「限定詞轉換」的正式定義載於下文。
[編輯] 相似型別
非正式地說,如果忽略頂層 cv 限定詞,兩個型別是相似的:
- 它們是相同型別;或
- 它們都是指標,且所指向的型別是相似的;或
- 它們都是指向同一類別成員的指標,且所指向成員的型別是相似的;或
- 它們都是陣列且陣列元素型別是相似的。
例如
- const int* const * 和 int** 是相似的;
- int (*)(int*) 和 int (*)(const int*) 不相似;
- const int (*)(int*) 和 int (*)(int*) 不相似;
- int (*)(int* const) 和 int (*)(int*) 是相似的(它們是相同型別);
- std::pair<int, int> 和 std::pair<const int, int> 不相似。
正式地,型別相似性是根據限定詞分解來定義的。
型別 T 的限定詞分解是一個由元件 cv_i 和 P_i 組成的序列,使得 T 在非負數 n 時為「cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U」,其中
- 每個
cv_i是一組 const 和 volatile,並且 - 每個
P_i是
- 「指向」,
- 「指向類別
C_i的成員(型別)」, - 「N_i 個陣列」,或
- 「未知界限的陣列」。
如果 P_i 指定一個陣列,則元素型別上的 cv 限定詞 cv_i+1 也被視為該陣列的 cv 限定詞 cv_i。
// T is “pointer to pointer to const int”, it has 3 qualification-decompositions: // n = 0 -> cv_0 is empty, U is “pointer to pointer to const int” // n = 1 -> cv_0 is empty, P_0 is “pointer to”, // cv_1 is empty, U is “pointer to const int” // n = 2 -> cv_0 is empty, P_0 is “pointer to”, // cv_1 is empty, P_1 is “pointer to”, // cv_2 is “const", U is “int” using T = const int**; // substitute any of the following type to U gives one of the decompositions: // U = U0 -> the decomposition with n = 0: U0 // U = U1 -> the decomposition with n = 1: pointer to [U1] // U = U2 -> the decomposition with n = 2: pointer to [pointer to [const U2]] using U2 = int; using U1 = const U2*; using U0 = U1*;
兩個型別 T1 和 T2 是相似的,如果它們各自存在一個限定詞分解,且兩個限定詞分解都滿足以下所有條件:
- 它們具有相同的 n。
U所表示的型別是相同的。- 對於所有 i,對應的
P_i元件是相同的,或者一個是「N_i 個陣列」,另一個是「未知界限的陣列」(C++20 起)。
// the qualification-decomposition with n = 2: // pointer to [volatile pointer to [const int]] using T1 = const int* volatile *; // the qualification-decomposition with n = 2: // const pointer to [pointer to [int]] using T2 = int** const; // For the two qualification-decompositions above // although cv_0, cv_1 and cv_2 are all different, // they have the same n, U, P_0 and P_1, // therefore types T1 and T2 are similar.
[編輯] 組合 cv 限定詞
在以下描述中,型別 Tn 的最長限定詞分解記為 Dn,其元件記為 cvn_i 和 Pn_i。
|
如果滿足以下所有條件,型別
兩個型別
|
(直到 C++20) |
|
兩個型別
如果 |
(自 C++20 起) |
// longest qualification-decomposition of T1 (n = 2): // pointer to [pointer to [char]] using T1 = char**; // longest qualification-decomposition of T2 (n = 2): // pointer to [pointer to [const char]] using T2 = const char**; // Determining the cv3_i and T_i components of D3 (n = 2): // cv3_1 = empty (union of empty cv1_1 and empty cv2_1) // cv3_2 = “const” (union of empty cv1_2 and “const” cv2_2) // P3_0 = “pointer to” (no array of unknown bound, use P1_0) // P3_1 = “pointer to” (no array of unknown bound, use P1_1) // All components except cv_2 are the same, cv3_2 is different from cv1_2, // therefore add “const” to cv3_k for each k in [1, 2): cv3_1 becomes “const”. // T3 is “pointer to const pointer to const char”, i.e., const char* const *. using T3 = /* the qualification-combined type of T1 and T2 */; int main() { const char c = 'c'; char* pc; T1 ppc = &pc; T2 pcc = ppc; // Error: T3 is not the same as cv-unqualified T2, // no implicit conversion. *pcc = &c; *pc = 'C'; // If the erroneous assignment above is allowed, // the const object “c” may be modified. }
請注意,在 C 程式語言中,const/volatile 只能添加到第一層。
char** p = 0; char * const* p1 = p; // OK in C and C++ const char* const * p2 = p; // error in C, OK in C++
函式指標轉換
void (*p)(); void (**pp)() noexcept = &p; // error: cannot convert to pointer to noexcept function struct S { typedef void (*p)(); operator p(); }; void (*q)() noexcept = S(); // error: cannot convert to pointer to noexcept function |
(自 C++17 起) |
[編輯] 安全布林問題
在 C++11 之前,設計一個可在布林語境中使用的類別(例如 if (obj) { ... })存在一個問題:給定一個使用者定義轉換函式,例如 T::operator bool() const;,隱式轉換序列允許在該函式呼叫之後進行一次額外的標準轉換序列,這意味著結果的 bool 可以轉換為 int,從而允許諸如 obj << 1; 或 int i = obj; 的程式碼。
一個早期的解決方案可以在 std::basic_ios 中看到,它最初定義了 operator void*,使得諸如 if (std::cin) {...} 的程式碼可以編譯,因為 void* 可轉換為 bool,但 int n = std::cout; 則無法編譯,因為 void* 不可轉換為 int。這仍然允許諸如 delete std::cout; 的無意義程式碼編譯。
許多 C++11 之前的第三方函式庫都採用了更精巧的解決方案,稱為安全布林慣用法。std::basic_ios 也透過LWG issue 468 允許了此慣用法,並且 operator void* 已被替換(參見備註)。
自 C++11 起,顯式布林轉換也可以用來解決安全布林問題。
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯應用於之前的 C++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| CWG 170 | C++98 | 成員指標轉換的行為不清楚 如果衍生類別沒有原始成員 |
現已明確化 |
| CWG 172 | C++98 | 列舉型別根據其底層型別進行提升 | 改為根據其值範圍 |
| CWG 330 (N4261) |
C++98 | 從 double* const (*p)[3] 的轉換 到 double const * const (*p)[3] 是無效的 |
變為有效 |
| CWG 519 | C++98 | 空指標值不保證 轉換為另一種指標型別時保留 |
始終保留 |
| CWG 616 | C++98 | 任何未初始化物件和指標物件的 左值到右值轉換的行為 無效值始終是未定義的 |
不確定的 unsigned char 被允許;使用無效指標 是實作定義的 |
| CWG 685 | C++98 | 列舉型別的底層型別 在整數提升中如果固定則不被優先考慮 |
優先考慮 |
| CWG 707 | C++98 | 整數到浮點數轉換 在所有情況下都有定義的行為 |
行為是未定義的,如果 正在轉換的值 超出目標範圍 |
| CWG 1423 | C++11 | std::nullptr_t 可轉換為 bool 在直接初始化和拷貝初始化中 |
僅限直接初始化 |
| CWG 1773 | C++11 | 出現在潛在求值表達式中的命名表達式 其中命名物件未被 ODR 使用的表達式可能 在左值到右值轉換期間仍被求值 |
未被求值 |
| CWG 1781 | C++11 | std::nullptr_t 到 bool 被認為是一種隱式 轉換,儘管它僅對直接初始化有效 |
不再被認為是 隱式轉換 |
| CWG 1787 | C++98 | 從不確定的 緩存在暫存器中的 unsigned char 讀取行為是未定義的 |
現已改為定義明確 |
| CWG 1981 | C++11 | 語境轉換考慮顯式轉換函式 | 不考慮 |
| CWG 2140 | C++11 | 從左值到右值轉換是否會 從 std::nullptr_t 左值從記憶體中獲取這些左值,這一點不清楚 |
不獲取 |
| CWG 2310 | C++98 | 對於衍生到基底的指標轉換以及 基底到衍生的成員指標轉換, 衍生類別型別可能是不完整的 |
必須是完整的 |
| CWG 2484 | C++20 | char8_t 和 char16_t 具有不同的整數 提升策略,但它們都可以容納 |
char8_t 應當以 與 char16_t 相同的方式提升 |
| CWG 2485 | C++98 | 涉及位元欄位的整數提升未明確指定 | 改進了規範 |
| CWG 2813 | C++23 | 當呼叫類別 pr值 的顯式 物件成員函式時,會發生暫時物件實體化 |
不會發生 在此情況下 |
| CWG 2861 | C++98 | 指向型別不可存取物件的指標可能 轉換為指向基底類別子物件的指標 |
在這種情況下,行為是 未定義的 |
| CWG 2879 | C++17 | pr值 上的暫時物件實體化轉換 作為期望 gl值 的運算子的運算元 |
在某些情況下未應用 |
| CWG 2899 | C++98 | 左值到右值轉換可以應用於 指定具有無效值表示的物件的左值 |
在這種情況下,行為是 未定義的 |
| CWG 2901 | C++98 | 從 unsigned int 左值到右值轉換的結果 引用值為 -1 的 int 物件的左值不明確 |
現已明確化 |
[編輯] 另請參閱
| C 語言文件中關於 隱式轉換 的內容
|