隱式轉換
當型別 T1
的表示式被用在不接受該型別但接受其他型別 T2
的語境中時,就會執行隱式轉換;特別是在以下情況:
- 當表示式作為引數用於呼叫宣告引數型別為
T2
的函式時; - 當表示式作為期望
T2
的運算子的運算元時; - 當初始化一個型別為
T2
的新物件時,包括函式返回T2
時使用的return
語句; - 當表示式用於 switch 語句時(
T2
為整型); - 當表示式用於 if 語句或迴圈時(
T2
為 bool)。
只有當存在一個從 T1
到 T2
的明確的*隱式轉換序列*時,程式才格式良好(可編譯)。
如果被呼叫的函式或運算子有多個過載,在構建了從 T1
到每個可用 T2
的隱式轉換序列後,過載決議規則將決定哪個過載被編譯。
注意:在算術表示式中,二元運算子運算元的隱式轉換的目標型別由一套獨立的規則決定:常用算術轉換。
目錄 |
[編輯] 轉換順序
隱式轉換序列按以下順序包含:
在考慮建構函式或使用者定義轉換函式的引數時,只允許一個標準轉換序列(否則使用者定義轉換可能被有效地連結)。當從一個非類型別轉換為另一個非類型別時,只允許一個標準轉換序列。
標準轉換序列按以下順序包含:
- 左值到右值轉換,
- *陣列到指標轉換*,以及
- 函式到指標轉換;
3) 零個或一個*函式指標轉換*;
|
(C++17 起) |
使用者定義轉換包含零個或一個非顯式單引數轉換建構函式或非顯式轉換函式呼叫。
當且僅當 T2
可以從 e 複製初始化時,表示式 e 被認為是*隱式可轉換為 T2
*,即對於某個虛構的臨時變數 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-expression 的引數(
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) }
[編輯] 值轉換
值轉換是改變表示式值類別的轉換。當表示式作為運算子的操作數出現,而運算子期望不同值類別的表示式時,就會發生值轉換。
- 每當 glvalue 作為需要 prvalue 的運算子的操作數出現時,會應用*左值到右值*、*陣列到指標*或*函式到指標*的標準轉換,將表示式轉換為 prvalue。
|
(C++17 起) |
[編輯] 左值到右值轉換
任何非函式、非陣列型別 T
的左值(C++11 前)任何非函式、非陣列型別 T
的glvalue(C++11 起) 可以隱式轉換為右值(C++11 前)prvalue(C++11 起)。
- 如果
T
不是類型別,則右值(C++11 前)prvalue(C++11 起) 的型別是T
的 cv 非限定版本。 - 否則,右值(C++11 前)prvalue(C++11 起) 的型別是
T
。
如果程式需要從不完全型別進行左值到右值轉換,則該程式格式錯誤。
給定 左值(C++11 前)glvalue(C++11 起) 所指的物件為 obj
|
(C++11 前) | ||||
|
(C++11 起) |
此轉換模擬將值從記憶體位置讀取到 CPU 暫存器的行為。
[編輯] 陣列到指標轉換
“N
個 T
陣列”或“未知邊界的 T
陣列”型別的左值或右值可以隱式轉換為“指向 T
的指標”型別的prvalue。如果陣列是 prvalue,則會發生臨時物件實質化。(C++17 起) 結果指標指向陣列的第一個元素(詳情請參閱陣列到指標退化)。
[編輯] 函式到指標轉換
函式型別的左值可以隱式轉換為prvalue指向該函式的指標。這不適用於非靜態成員函式,因為不存在引用非靜態成員函式的左值。
臨時物件實質化任何完整型別 如果 struct S { int m; }; int i = S().m; // member access expects glvalue as of C++17; // S() prvalue is converted to xvalue 臨時物件實質化發生在以下情況:
請注意,當從相同型別的 prvalue 初始化物件時(透過直接初始化或複製初始化),不會發生臨時物件實質化:這樣的物件直接從初始化器初始化。這確保了“保證的複製省略”。 |
(C++17 起) |
[編輯] 整型提升
小型整型(例如 char)和無作用域列舉型別的prvalue 可以轉換為較大整型(例如 int)的 prvalue。特別是,算術運算子不接受小於 int 的型別作為引數,如果適用,在左值到右值轉換後會自動應用整型提升。此轉換始終保留值。
本節中的以下隱式轉換被歸類為*整型提升*。
請注意,對於給定源型別,整型提升的目標型別是唯一的,所有其他轉換都不是提升。例如,過載決議會選擇 char -> int(提升)而非 char -> short(轉換)。
[編輯] 從整型提升
型別 bool 的 prvalue 可以轉換為型別 int 的 prvalue,其中 false 變為 0,true 變為 1。
對於除 bool 外的整型 T
的 prvalue val
- 如果 int 可以表示位域的所有值,則 val 可以轉換為型別 int 的 prvalue;
- 否則,如果 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 的 prvalue; - 否則,val 可以轉換為型別 unsigned int 的 prvalue。
- 如果 int 可以表示
T
是給定字元型別之一)指定的案例中,val 可以轉換為以下型別中第一個可以表示其底層型別所有值的 prvalue- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
[編輯] 從列舉型別提升
底層型別不固定的無作用域列舉型別的 prvalue 可以轉換為以下列表中第一個能夠容納其整個值範圍的型別的 prvalue
- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
底層型別固定的無作用域列舉型別的 prvalue 可以轉換為其底層型別。此外,如果底層型別也受整型提升影響,則轉換為提升後的底層型別。為了過載決議的目的,轉換為未提升的底層型別更好。 |
(C++11 起) |
[編輯] 浮點型提升
型別 float 的prvalue 可以轉換為型別 double 的 prvalue。值不會改變。
此轉換稱為*浮點型提升*。
[編輯] 數值轉換
與提升不同,數值轉換可能會改變值,並可能導致精度損失。
[編輯] 整型轉換
整型或無作用域列舉型別的prvalue 可以轉換為任何其他整型。如果轉換列在整型提升下,則它是一種提升而不是轉換。
- 如果目標型別是無符號的,則結果值是等於源值模 2n
的最小無符號值,其中 n 是用於表示目標型別的位數。
- 也就是說,根據目標型別是更寬還是更窄,有符號整數分別進行符號擴充套件[1]或截斷,無符號整數分別進行零擴充套件或截斷。
- 如果目標型別是有符號的,並且源整數可以在目標型別中表示,則值不會改變。否則,結果是實現定義的(C++20 前)目標型別中唯一的值,該值等於源值模 2n
,其中 n 是用於表示目標型別的位數(C++20 起)(請注意,這與有符號整數算術溢位不同,後者是未定義的)。 - 如果源型別是 bool,則值 false 轉換為零,值 true 轉換為目標型別的值一(請注意,如果目標型別是 int,這是一種整型提升,而不是整型轉換)。
- 如果目標型別是 bool,這是一種布林轉換(見下文)。
[編輯] 浮點型轉換
浮點型別的prvalue 可以轉換為任何其他浮點型別的 prvalue。 |
(直至 C++23) |
浮點型別的prvalue 可以轉換為任何其他浮點型別的 prvalue,且該目標浮點型別的浮點轉換等級大於或等於源型別。 標準浮點型別的prvalue 可以轉換為任何其他標準浮點型別的 prvalue。
|
(C++23 起) |
如果轉換列在浮點提升下,則它是一種提升而不是轉換。
- 如果源值可以在目標型別中精確表示,則它不會改變。
- 如果源值介於目標型別的兩個可表示值之間,則結果是這兩個值之一(具體是哪一個由實現定義,儘管如果支援 IEEE 算術,則預設四捨五入到最近)。
- 否則,行為未定義。
[編輯] 浮點-整型轉換
浮點型別的prvalue 可以轉換為任何整型型別的 prvalue。小數部分被截斷,即小數部分被丟棄。
- 如果截斷後的值不能適合目標型別,則行為是未定義的(即使目標型別是無符號的,模算術也不適用)。
- 如果目標型別是 bool,這是一種布林轉換(見下文)。
整型或無作用域列舉型別的 prvalue 可以轉換為任何浮點型別的 prvalue。如果可能,結果是精確的。
- 如果值可以適合目標型別但不能精確表示,則選擇最接近的更高或更低的可表示值是實現定義的,儘管如果支援 IEEE 算術,則預設四捨五入到最近。
- 如果值不能適合目標型別,則行為是未定義的。
- 如果源型別是 bool,則值 false 轉換為零,值 true 轉換為一。
[編輯] 指標轉換
空指標常量可以轉換為任何指標型別,結果是該型別的空指標值。這種轉換(稱為*空指標轉換*)允許作為單次轉換轉換為 cv 限定型別,即它不被視為數值轉換和限定符轉換的組合。
指向任何(可選 cv 限定的)物件型別 T
的prvalue 指標可以轉換為指向(相同 cv 限定的)void 的 prvalue 指標。結果指標表示與原始指標值相同的記憶體位置。
- 如果原始指標是空指標值,則結果是目標型別的空指標值。
型別為“指向(可能帶有 cv 限定符的)Derived
的指標”的 prvalue ptr 可以轉換為型別為“指向(可能帶有 cv 限定符的)Base
的指標”的 prvalue,其中 Base
是 Derived
的基類,且 Derived
是一個完全的類型別。如果 Base
不可訪問或不明確,則程式格式錯誤。
- 如果 ptr 是空指標值,則結果也是空指標值。
- 否則,如果
Base
是Derived
的虛基類,並且 ptr 不指向型別與Derived
相似且在其生命週期內或其構造或析構期間的物件,則行為是未定義的。 - 否則,結果是指向派生類物件中基類子物件的指標。
[編輯] 成員指標轉換
空指標常量可以轉換為任何成員指標型別,結果是該型別的空成員指標值。這種轉換(稱為*空成員指標轉換*)允許作為單次轉換轉換為 cv 限定型別,即它不被視為數值轉換和限定符轉換的組合。
型別為“指向 Base
中型別為(可能帶有 cv 限定符的)T
的成員的指標”的prvalue 可以轉換為型別為“指向 Derived
中型別為(相同 cv 限定的)T
的成員的指標”的 prvalue,其中 Base
是 Derived
的基類,且 Derived
是一個完整的類型別。如果 Base
不可訪問、不明確、是 Derived
的虛基類或 Derived
的某個中間虛基類的基類,則程式格式錯誤。
- 如果
Derived
不包含原始成員,也不是包含原始成員的類的基類,則行為是未定義的。 - 否則,結果指標可以用
Derived
物件解引用,它將訪問該Derived
物件中Base
基子物件內的成員。
[編輯] 布林轉換
整型、浮點型、無作用域列舉、指標和成員指標型別的prvalue 可以轉換為型別 bool 的 prvalue。
零值(對於整型、浮點型和無作用域列舉)以及空指標和空成員指標值變為 false。所有其他值變為 true。
在直接初始化的語境中,bool 物件可以從型別 std::nullptr_t 的 prvalue 初始化,包括 nullptr。結果值為 false。然而,這不被認為是隱式轉換。 |
(C++11 起) |
[編輯] 限定符轉換
一般來說:
- 指向cv 限定型別
T
的指標的prvalue 可以轉換為指向更 cv 限定的相同型別T
的 prvalue(換句話說,可以新增 constness 和 volatility)。 - 指向類
X
中 cv 限定型別T
的成員的指標的 prvalue 可以轉換為指向類X
中更 cv 限定型別T
的成員的指標的 prvalue。
“限定符轉換”的正式定義見下文。
[編輯] 相似型別
非正式地說,如果忽略頂層 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
是“cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U
”,其中非負數 n,其中
- 每個
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
表示的型別相同。- 對應的
P_i
元件對於所有 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 起,顯式 bool 轉換也可用於解決安全布林問題。
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
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 中讀取的行為是未定義的 讀取不確定 unsigned char 的行為是未定義的 |
已明確定義 |
CWG 1981 | C++11 | 上下文轉換考慮顯式轉換函式 | 不考慮 |
CWG 2140 | C++11 | 從 std::nullptr_t 左值進行的左值到右值轉換是否從記憶體中獲取這些左值,這一點不明確 std::nullptr_t 左值是否從記憶體中獲取這些左值,這一點不明確 |
不獲取 |
CWG 2310 | C++98 | 對於派生到基的指標轉換和 基到派生的成員指標轉換, 派生類型別可以不完整 |
必須完整 |
CWG 2484 | C++20 | char8_t 和 char16_t 的整型提升策略不同,但它們都可以容納 它們 |
char8_t 應該以與 char16_t 相同的方式提升 與 char16_t 相同的方式 |
CWG 2485 | C++98 | 涉及位域的整型提升未明確說明 | 改進了規範 |
CWG 2813 | C++23 | 當呼叫類 prvalue 的顯式物件成員函式時,將發生臨時物件實質化 不會發生 |
不會發生 在這種情況下 |
CWG 2861 | C++98 | 指向型別不可訪問物件的指標可以轉換為指向基類子物件的指標 轉換為指向基類子物件的指標 |
在這種情況下,行為是 未定義的 |
CWG 2879 | C++17 | 臨時物件實質化轉換應用於作為期望 glvalue 的運算子運算元的 prvalue 在某些情況下不適用 |
在某些情況下不適用 |
CWG 2899 | C++98 | 左值到右值轉換可以應用於具有無效值表示的左值 指示具有無效值表示的物件 |
在這種情況下,行為是 未定義的 |
CWG 2901 | C++98 | 從引用值為 -1 的 int 物件的 unsigned int 左值進行左值到右值轉換的結果不明確 引用值為 -1 的 int 物件 |
已明確 |
[編輯] 另請參閱
C 文件 關於 隱式轉換
|