暗黙の型変換
暗黙の型変換は、ある型T1の式が、その型は受け入れないが別の型T2は受け入れるコンテキストで使用される場合に実行されます。特に、
- 式が、
T2をパラメータとして宣言された関数を呼び出す際の引数として使用される場合。 - 式が、
T2を期待する演算子のオペランドとして使用される場合。 T2型の新しいオブジェクトを初期化する場合(T2を返す関数のreturn文を含む)。- 式が
switch文で使用される場合(T2は整数型)。 - 式が
if文またはループで使用される場合(T2はbool)。
プログラムは、T1からT2への一意の暗黙の変換シーケンスが存在する場合にのみ、正しく記述されている(コンパイルされる)とみなされます。
呼び出される関数または演算子のオーバーロードが複数ある場合、T1から各利用可能なT2への暗黙の変換シーケンスが構築された後、オーバーロード解決の規則によってどのオーバーロードがコンパイルされるかが決定されます。
注:算術式では、二項演算子のオペランドに対する暗黙の型変換の宛先型は、別途の規則セットによって決定されます:通常の算術変換。
目次 |
[編集] 変換の順序
暗黙の変換シーケンスは、次の順序で構成されます。
コンストラクタまたはユーザー定義変換関数への引数を考慮する場合、標準変換シーケンスは1つのみ許可されます(そうでなければ、ユーザー定義変換が実質的に連鎖される可能性があります)。非クラス型から別の非クラス型への変換では、標準変換シーケンスのみが許可されます。
標準変換シーケンスは、次の順序で構成されます。
- lvalueからrvalueへの変換,
- 配列からポインタへの変換、および
- 関数からポインタへの変換;
|
3) 0回または1回の関数ポインタ変換。
|
(C++17以降) |
ユーザー定義変換は、0回または1回の非explicit単一引数変換コンストラクタまたは非explicit変換関数の呼び出しで構成されます。
式eは、T2に暗黙に変換可能であるといいます。これは、T2がeからコピー初期化できる場合、つまり、一時的なtを導入した場合の宣言T2 t = e;が正しく記述されている(コンパイル可能である)場合にのみ当てはまります。これは、一時的なtを導入した場合の宣言T2 t(e);である直接初期化とは異なることに注意してください。直接初期化では、明示的なコンストラクタと変換関数も考慮されます。
[編集] コンテキスト依存の変換
|
以下のコンテキストでは、型
|
(C++11以降) |
以下のコンテキストでは、コンテキスト依存の型Tが期待され、クラス型Eの式eは、
|
(C++14まで) |
|
(C++14以降) |
このような式eは、指定された型Tにコンテキストに応じて暗黙に変換されるといいます。明示的な変換関数は、boolへのコンテキスト依存の変換で考慮される場合でも、考慮されないことに注意してください。(since 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) }
[編集] 値の変換
値の変換とは、式の値カテゴリを変更する変換です。これは、式が異なる値カテゴリを期待する演算子のオペランドとして現れるたびに発生します。
- glvalueが、そのオペランドにprvalueを必要とする演算子のオペランドとして現れる場合、lvalueからrvalueへの変換、配列からポインタへの変換、または関数からポインタへの変換の標準変換が、式をprvalueに変換するために適用されます。
|
(C++17以降) |
[編集] lvalueからrvalueへの変換
関数型でも配列型でもない任意の型Tのlvalue(until C++11)任意の型Tのglvalue(since C++11)は、rvalue(until C++11)prvalue(since C++11)に暗黙に変換できます。
Tがクラス型でない場合、rvalue(until C++11)prvalue(since C++11)の型は、Tのcv修飾されていないバージョンです。- それ以外の場合、rvalue(until C++11)prvalue(since C++11)の型は
Tです。
プログラムが不完全型からのlvalue-to-rvalue変換を必要とする場合、そのプログラムは不正な形式となります。
lvalue(until C++11)glvalue(since C++11)が参照するオブジェクトをobjとした場合。
|
(C++11まで) | ||||
|
(C++11以降) |
この変換は、メモリ位置からCPUレジスタに値を読み取る行為をモデル化します。
[編集] 配列からポインタへの変換
「N要素のT型配列」または「T型配列(境界不明)」という型のlvalueまたはrvalueは、「T型へのポインタ」という型のprvalueに暗黙に変換できます。配列がprvalueの場合、一時オブジェクトの生成が発生します。(since C++17)結果のポインタは、配列の最初の要素を指します(詳細は配列からポインタへの減衰を参照)。
[編集] 関数からポインタへの変換
関数型のlvalueは、その関数へのprvalueポインタに暗黙に変換できます。これは非静的メンバ関数には適用されません。なぜなら、非静的メンバ関数を指すlvalueは存在しないためです。
一時オブジェクトの生成任意の完全型
struct S { int m; }; int i = S().m; // member access expects glvalue as of C++17; // S() prvalue is converted to xvalue 一時オブジェクトの生成は、次の状況で発生します。
一時オブジェクトの生成は、prvalueと同じ型のオブジェクトを(直接初期化またはコピー初期化によって)prvalueから初期化する際には行われないことに注意してください。そのようなオブジェクトは初期化子から直接初期化されます。これにより「保証されたコピー削除」が実現されます。 |
(C++17以降) |
[編集] 整数昇格
小さな整数型(charなど)およびスコープなし列挙型のprvalueは、より大きな整数型(intなど)のprvalueに変換されることがあります。特に、算術演算子は、intより小さい型を引数として受け入れず、lvalue-to-rvalue変換後に整数昇格が自動的に適用されます(該当する場合)。この変換は常に値を保持します。
このセクションの次の暗黙の型変換は、整数昇格として分類されます。
特定のソース型に対して、整数昇格の宛先型は一意であり、他のすべての変換は昇格ではないことに注意してください。たとえば、オーバーロード解決は、char → short(変換)よりもchar → int(昇格)を選択します。
[編集] 整数型からの昇格
bool型のprvalueは、int型のprvalueに変換できます。falseは0に、trueは1になります。
整数型T(boolを除く)のprvaluevalについて。
valがビットフィールドに対するlvalue-to-rvalue変換の結果である場合。-
intがビットフィールドのすべての値を表すことができる場合、valはint型のprvalueに変換できます。 - それ以外の場合、
unsigned intがビットフィールドのすべての値を表すことができる場合、valはunsigned intに変換できます。 - それ以外の場合、
valは項目(3)で指定された規則に従って変換できます。
valはビットフィールドから変換されていない)。Tがchar8_t、(since C++20)char16_t、char32_tまたは(since C++11)wchar_tの場合、valは項目(3)で指定された規則に従って変換できます。- それ以外の場合、
Tの整数変換ランクがintのランクより低い場合。
-
intがTのすべての値を表すことができる場合、valはint型のprvalueに変換できます。 - それ以外の場合、
valはunsigned int型のprvalueに変換できます。
-
unsigned intに収まらない場合)または項目(2)(Tが指定された文字型の一つである場合)で指定された場合、valは、その基底型(underlying type)のすべての値を表すことができる次の型リストの最初の型への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]または切り捨てられ、符号なし整数はそれぞれゼロ拡張または切り捨てられます。
- 宛先型が符号付きの場合、ソース整数が宛先型で表せる場合、値は変更されません。それ以外の場合、結果は実装定義(until C++20)宛先型でソース値 modulo 2n (ここでnは宛先型を表すために使用されるビット数)に等しい一意の値(since C++20)になります(これは未定義の符号付き整数算術オーバーフローとは異なることに注意)。
- ソース型が
boolの場合、値falseはゼロに、値trueは宛先型の値1に変換されます(宛先型がintの場合、これは整数変換ではなく整数昇格であることに注意)。 - 宛先型が
boolの場合、これはブール変換(下記参照)です。
[編集] 浮動小数点変換
|
浮動小数点型のprvalueは、他の任意の浮動小数点型へのprvalueに変換できます。 |
(C++23まで) |
|
浮動小数点型のprvalueは、浮動小数点変換ランクがより大きいか等しい任意の浮動小数点型へのprvalueに変換できます。 標準浮動小数点型のprvalueは、他の任意の標準浮動小数点型へのprvalueに変換できます。
|
(C++23から) |
変換が浮動小数点昇格の下でリストされている場合、それは変換ではなく昇格です。
- ソース値を宛先型で正確に表すことができる場合、値は変更されません。
- ソース値が宛先型の2つの表現可能な値の間にある場合、結果はそれら2つの値のいずれかになります(どちらになるかは実装定義ですが、IEEE算術がサポートされている場合、丸めはデフォルトで最近接になります)。
- それ以外の場合、動作は未定義です。
[編集] 浮動小数点数と整数の間の変換
浮動小数点型のprvalueは、任意の整数型へのprvalueに変換できます。小数部分は切り捨てられます(つまり、小数部分は破棄されます)。
- 切り捨てられた値が宛先型に収まらない場合、動作は未定義です(宛先型が符号なしの場合でも、剰余算術は適用されません)。
- 宛先型が
boolの場合、これはブール変換(下記参照)です。
整数型またはスコープなし列挙型のprvalueは、任意の浮動小数点型へのprvalueに変換できます。可能な場合は正確な結果になります。
- 値が宛先型に収まるが正確に表現できない場合、最も近い上位の表現可能な値または最も近い下位の表現可能な値のどちらが選択されるかは実装定義ですが、IEEE算術がサポートされている場合、丸めはデフォルトで最近接になります。
- 値が宛先型に収まらない場合、動作は未定義です。
- ソース型が
boolの場合、値falseはゼロに、値trueは1に変換されます。
[編集] ポインタ変換
ヌルポインタ定数は任意のポインタ型に変換でき、結果はその型のヌルポインタ値になります。このような変換(ヌルポインタ変換として知られる)は、cv修飾型への単一の変換として許可され、数値変換とcv修飾子変換の組み合わせとはみなされません。
任意の(オプションでcv修飾された)オブジェクト型Tへのポインタのprvalueは、(同様にcv修飾された)voidへのprvalueポインタに変換できます。結果のポインタは、元のポインタ値と同じメモリ位置を表します。
- 元のポインタがヌルポインタ値の場合、結果も宛先型のヌルポインタ値になります。
BaseがDerivedの基底クラスであり、Derivedが完全なクラス型である場合、「Baseへのポインタ(cv修飾されている可能性のある)」という型のprvalueptrは、「Derivedへのポインタ(同様にcv修飾されている)」という型のprvalueに変換できます。Baseがアクセス不可能または曖昧な場合、プログラムは不正な形式となります。
ptrがヌルポインタ値の場合、結果もヌルポインタ値になります。- それ以外の場合、
BaseがDerivedの仮想基底クラスであり、ptrがDerivedに類似した型類似のオブジェクトを指しておらず、その生存期間内または構築・破棄期間内ではない場合、動作は未定義です。 - それ以外の場合、結果は派生クラスオブジェクトの基底クラスサブオブジェクトへのポインタになります。
[編集] ポインタメンバー変換
ヌルポインタ定数は任意のポインタメンバー型に変換でき、結果はその型のヌルメンバーポインタ値になります。このような変換(ヌルメンバーポインタ変換として知られる)は、cv修飾型への単一の変換として許可され、数値変換とcv修飾子変換の組み合わせとはみなされません。
「Baseのメンバ(cv修飾されている可能性のある)Tへのポインタ」という型のprvalueは、「Derivedのメンバ(同様にcv修飾されている)Tへのポインタ」という型のprvalueに変換できます。ここで、BaseはDerivedの基底クラスであり、Derivedは完全なクラス型です。Baseがアクセス不可能、曖昧、またはDerivedの仮想基底である場合、プログラムは不正な形式となります。
Derivedが元のメンバを含んでおらず、元のメンバを含むクラスの基底クラスでない場合、動作は未定義です。- それ以外の場合、結果のポインタは
Derivedオブジェクトで逆参照でき、そのDerivedオブジェクトのBase基底サブオブジェクト内のメンバにアクセスします。
[編集] ブール変換
整数型、浮動小数点型、スコープなし列挙型、ポインタ型、およびポインタメンバー型のprvalueは、bool型へのprvalueに変換できます。
ゼロの値(整数、浮動小数点、スコープなし列挙の場合)とヌルポインタおよびヌルポインタメンバーの値はfalseになります。その他のすべての値はtrueになります。
|
直接初期化のコンテキストでは、 |
(C++11以降) |
[編集] cv修飾子変換
一般的に。
- cv修飾された型
Tへのポインタ型のprvalueは、よりcv修飾された同じ型Tへのprvalueポインタに変換できます(つまり、constnessとvolatilityを追加できます)。 - クラス
Xのcv修飾された型Tのメンバへのポインタ型のprvalueは、クラスXのよりcv修飾された型Tのメンバへのprvalueポインタに変換できます。
「cv修飾子変換」の正式な定義は以下で定義されています。
[編集] 類似型
非公式には、2つの型は、トップレベルの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>は類似していません。
正式には、型類似性はcv修飾分解(qualification-decomposition)によって定義されます。
型Tのcv修飾分解は、コンポーネント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のメンバ(型は)」 - 「Ni要素の配列」または
- 「境界不明の配列」
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*;
2つの型T1とT2は、それぞれに対してcv修飾分解が存在し、すべての次の条件が2つのcv修飾分解に対して満たされる場合に類似しています。
- それらは同じ
nを持っています。 Uによって示される型は同じです。- 対応する
P_iコンポーネントは同じですまたは、一方は「Ni要素の配列」で、もう一方は「境界不明の配列」です。(since C++20)、すべてのiに対して。
// 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の最も長いcv修飾分解はDnと示され、そのコンポーネントはcvn_iとPn_iと示されます。
|
型
2つの型
|
(C++20まで) |
|
2つの型
型 |
(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;)がある場合、その関数呼び出しの後に追加の標準変換シーケンスが1つ許可されたため、結果のboolはintに変換でき、obj << 1;やint i = obj;のようなコードが可能でした。
これに対する初期の解決策の1つはstd::basic_iosに見られます。これは当初operator void*を定義していたため、if (std::cin) { ... }のようなコードはvoid*がboolに変換可能であるためコンパイルされますが、int n = std::cout;はvoid*がintに変換可能でないためコンパイルされません。これは、delete std::cout;のような無意味なコードをコンパイル可能にしたままにしていました。
C++11より前の多くのサードパーティライブラリは、Safe Boolイディオムとして知られる、より洗練されたソリューションで設計されていました。std::basic_iosもLWG issue 468を通じてこのイディオムを許可し、operator void*は(注を参照)置き換えられました。
C++11以降、明示的なbool変換もセーフブール問題を解決するために使用できます。
[編集] 不具合報告
以下の動作変更を伴う欠陥報告が、以前に公開された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 | 未初期化オブジェクトおよびポインタオブジェクトの lvalueからrvalueへの変換の 無効な値の動作は常に未定義でした。 |
未定義のunsigned charは許可されました。無効なポインタの使用は 実装定義です。 |
| CWG 685 | C++98 | 列挙型の基底型は 基底型が固定されている場合、整数昇格で優先されませんでした。 |
優先されるようになりました。 |
| CWG 707 | C++98 | 整数から浮動小数点数への変換 すべての場合で定義された動作でした。 |
未定義の動作は 変換される値が 宛先範囲外の場合です。 |
| CWG 1423 | C++11 | std::nullptr_tはboolに変換可能でした。直接初期化とコピー初期化の両方で。 |
直接初期化のみで。 |
| CWG 1773 | C++11 | 名前式が評価対象 式に出現し、名前付けされたオブジェクトがODR使用されない場合、lvalue-to-rvalue変換中に 依然として評価される可能性がありました。 |
評価されなくなりました。 |
| CWG 1781 | C++11 | std::nullptr_tからboolへの変換は暗黙の変換とみなされていましたが、直接初期化でのみ有効でした。 |
暗黙の変換とはみなされなくなりました。 暗黙の変換ではありません。 |
| CWG 1787 | C++98 | 未定義のunsigned charのレジスタにキャッシュされた |
符号ビットにシフトする動作は定義された |
| 値表現からの読み取りの動作は未定義でした。 | C++11 | CWG 1981 | 考慮されなかった |
| コンテキスト依存の変換は、明示的な変換関数を考慮していました。 | C++11 | CWG 2140 lvalue-to-rvalue変換が |
std::nullptr_t lvalueからメモリをフェッチするかどうかは不明でした。 |
| フェッチされません。 | C++98 | CWG 2310 派生クラスから基底クラスへのポインタ変換および 基底クラスから派生クラスへのポインタメンバー変換において、 |
派生クラス型が不完全である可能性がありました。 |
| 完全である必要があります。 | C++20 | CWG 2484char8_tとchar16_tは異なる整数 |
昇格戦略を持っていましたが、両方とも適合します。char8_tは、char16_tと同じ方法で昇格されるべきです。 |
| CWG 2485 | C++98 | ビットフィールドを含む整数昇格の仕様が不明確でした。 | 仕様が改善されました。 |
| CWG 2813 | C++23 | クラスprvalueの明示的な メンバ関数が呼び出されたときに、一時オブジェクトの生成が発生しました。 |
発生しません。 この場合に |
| CWG 2861 | C++98 | 型にアクセスできないオブジェクトへのポインタが 基底クラスサブオブジェクトへのポインタに変換される可能性がありました。 |
この場合の動作は 未定義となる |
| CWG 2879 | C++17 | 一時オブジェクトの生成変換がprvalueに適用されました。 glvalueを期待する演算子のオペランドとして。 |
一部のケースでは適用されませんでした。 |
| CWG 2899 | C++98 | lvalue-to-rvalue変換は、lvalueに適用される可能性がありました。 無効な値表現を持つオブジェクトを指定する。 |
この場合の動作は 未定義となる |
| CWG 2901 | C++98 | intオブジェクトを参照するunsigned int lvalueからのlvalue-to-rvalue変換の結果。値が -1の場合、不明確でした。 |
明確化された |
[編集] 関連項目
| Cドキュメント Implicit conversions
|