暗黙の変換
暗黙の変換は、何らかの型 T1
の式が、その型を受理しないけれども何らかの別の型 T2
を受理する文脈で使用されたときに行われます。 特に、
- 引数として
T2
で宣言された関数を呼ぶときに引数としてその式が使用されたとき。 -
T2
を期待する演算子で被演算子としてその式が使用されたとき。 -
T2
型の新しいオブジェクトを初期化するとき (T2
を返す関数内でのreturn
文を含みます)。 -
switch
文でその式が使用されたとき (T2
は整数型です)。 -
if
文やループでその式が使用されたとき (T2
は bool です)。
T1
から T2
への曖昧でない暗黙の変換シーケンスがひとつ存在する場合にのみ、プログラムは well-formed です (コンパイルできます)。
呼ばれる関数または演算子のオーバーロードが複数存在する場合は、 T1
から利用可能なそれぞれの T2
への暗黙の変換シーケンスが組み立てられた後、オーバーロード解決のルールにより、どのオーバーロードがコンパイルされるかが決定されます。
ノート: 算術式では、二項演算子の被演算子に対する暗黙の変換のための結果の型は、通常の算術変換という別のルールの集合によって決定されます。
目次 |
[編集] 変換の順序
暗黙の変換シーケンスは以下の内容から (この順序で) 構成されます。
コンストラクタまたはユーザ定義変換関数への引数を考慮するときは、1個の標準変換シーケンスだけが使用できます (そうでなければユーザ定義変換が実質的に連鎖できます)。 ある組み込み型から別の組み込み型に変換するときは、1個の標準変換シーケンスだけが使用できます。
標準変換シーケンスは以下の内容から (この順序で) 構成されます。
3) 0個または1個の関数ポインタ変換。
|
(C++17以上) |
ユーザ定義変換は0個または1個の非 explicit 単一引数コンストラクタまたは非 explicit 変換関数の呼び出しから構成されます。
T2
が e
からコピー初期化できる、つまり、何らかの架空の一時変数 t
について宣言 T2 t = e; が well-formed である (コンパイルできる) 場合に限り、式 e
は T2
に暗黙に変換可能であると言います。 これは直接初期化 (T2 t(e)) と異なることに注意してください。 直接初期化では explicit コンストラクタおよび変換関数が追加で考慮されます。
[編集] 文脈的な変換
以下の文脈では、 bool 型が期待され、宣言 bool t(e); が well-formed であれば (つまり
|
(C++11以上) |
以下の文脈では、文脈固有の型 T
が期待され、クラス型 E
の式 e
は、 E
が許容可能な型への非 explicit ユーザ定義変換関数をひとつ持つ (C++14未満)戻り値の型が T
(cv 修飾されていても構いません) または T
(cv 修飾されていても構いません) への参照である非 explicit 変換関数を E
が持つような型 T
が許容可能な型の中でちょうど1個存在し、 e
が T
に暗黙に変換可能である (C++14以上)場合にのみ、使用できます。 そのような式 e
は指定された型 T
に文脈的に暗黙に変換されると言います。 explicit 変換関数は (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) { } // C++14 未満はエラー (変換関数が2個以上あります)。 // C++14 以上は OK (両方の関数が同じ型 int に変換します)。 switch(i + 0) { } // 常に OK (暗黙の変換)。 }
[編集] 値変換
値変換は式の値カテゴリを変更する変換です。 異なる値カテゴリの式を期待する演算子の被演算子として式が現れたときに行われます。
[編集] 左辺値から右辺値への変換
任意の非関数非配列型 T
の glvalue は同じ型の prvalue に暗黙に変換できます。 T
が非クラス型の場合、この変換は cv 修飾も削除します。 glvalue が std::nullptr_t 型の場合、結果の prvalue はヌルポインタ定数 nullptr です。
未評価文脈 (sizeof、 typeid、 noexcept または decltype の被演算子) の中で遭遇したのでない限り、この変換は
実質的にコンストラクタ引数として元の glvalue を用いて T
型の一時オブジェクトをコピー構築し、その一時オブジェクトが prvalue として返されます。
(C++17未満)
glvalue を結果の値がその glvalue でコピー初期化される prvalue に変換します。
(C++17以上)
この変換はメモリ位置から CPU レジスタに値を読み込む動作をモデル化します。
glvalue の参照先のオブジェクトが不定値を格納している場合 (非クラスの自動変数をデフォルト初期化することによって取得した場合など)、動作は未定義です。
ただし、その不定値が CPU レジスタにキャッシュされていなかった符号なし文字型 (cv 修飾されていても構いません) である場合は除きます。 CPU レジスタにキャッシュされていなかったとは、形式的には、以下のいずれかです。
glvalue が |
(C++11以上) |
[編集] 配列からポインタへの変換
「N
個の T
の配列」または「境界が未知な T
の配列」型の lvalue または rvalue は、「T
へのポインタ」型の prvalue に暗黙に変換できます。 配列が prvalue の場合は、一時具体化が発生します。 (C++17以上) 結果のポインタはその配列の最初の要素を参照します (詳細については配列からポインタへの降格を参照してください)。
一時具体化任意の完全型 struct S { int m; }; int i = S().m; // C++17 以上では、メンバアクセスは glvalue を期待します。 // S() の prvalue は xvalue に変換されます。 一時具体化は以下の状況で発生します。
(直接初期化またはコピー初期化によって) 同じ型の prvalue からオブジェクトを初期化するとき、一時具体化は発生しないことに注意してください。 そのようなオブジェクトは初期化子から直接初期化されます。 これによって「保証されたコピー省略」が保証されます。 |
(C++17以上) |
[編集] 関数からポインタへの変換
関数型 T
の lvalue はその関数へのポインタの prvalue に暗黙に変換されます。 非静的メンバ関数を参照する lvalue は存在しないため、これは非静的メンバ関数には適用されません。
[編集] 数値昇格
[編集] 整数昇格
小さな整数型 (char など) の prvalue は、より大きな整数型 (int など) の prvalue に変換できます。 特に、算術演算子は引数として int より小さな型を受理せず、左辺値から右辺値への変換後、整数昇格が自動的に適用されます (適用可能であれば)。 この変換は常に値を維持します。
以下の暗黙の変換は整数昇格に分類されます。
-
signed char
またはsigned short
は int に変換できます。 -
unsigned char
、char8_t
(C++20以上) またはunsigned short
は、その値の範囲全体を保持することができる場合は int に、そうでなければ unsigned int に変換できます。 -
char
は、そのベースとなる型 (signed char または unsigned char (上を参照)) によって、 int または unsigned int に変換できます。 -
wchar_t
、char16_t
およびchar32_t
(C++11以上) は、 int、 unsigned int、 long、 unsigned long、 long long、 unsigned long long (C++11以上) のうち、その値の範囲全体を保持することができる最初の型に変換できます。 - ベースとなる型が固定されていないスコープなし列挙は、 int、 unsigned int、 long、 unsigned long、 long long、 unsigned long long または拡張整数型 (サイズの順で、符号付きが符号なしより優先されます) (C++11以上) のうち、その値の範囲全体を保持することができる最初の型に変換できます。 値の範囲がより大きい場合は、整数昇格は適用されません。
-
|
(C++11以上) |
- ビットフィールドは、そのビットフィールドの値の範囲全体を int で表現できる場合は int に変換でき、そうでなくそのビットフィールドの値の範囲全体を unsigned int で表現できる場合は unsigned int に変換でき、そうでなければ整数昇格は適用されません。
- bool 型は int に変換できます。 値 false は 0 になり、値 true は 1 になります。
他のすべての変換は昇格ではないことに注意してください。 例えば、オーバーロード解決は char → short (変換) よりも char → int (昇格) を選択します。
[編集] 浮動小数点昇格
float 型の prvalue は double 型の prvalue に変換できます。 値は変更されません。
[編集] 数値変換
昇格と異なり、数値変換は値を変更することがあります。 これは精度の喪失を伴うことがあります。
[編集] 整数変換
整数型またはスコープなし列挙型の prvalue は、任意の他の整数型に変換できます。 変換が整数昇格の項目に掲載されている場合、それは昇格であって、変換ではありません。
- 結果の型が符号なしの場合、結果の値は x modulo 2n
と等しい最も小さな符号なしの値です。 ただし x は元の値で、 n は結果の型を表現するために使用されるビット数です。
- つまり、結果の型が広くなるか狭くなるかによって、符号付き整数は符号拡張[注釈 1]されるか切り捨てられ、符号なし整数はゼロ拡張されるか切り捨てられます。
- 結果の型が符号付きの場合、元の整数が結果の型で表現可能であれば、値は変更されません。 そうでなければ、結果は処理系定義です (C++20未満) x modulo 2n
に等しい結果の型の一意な値です (ただし x は元の値で n は結果の型を表現するために使用されるビット数です) (C++20以上) (これは符号付整数算術のオーバーフロー (未定義です) と異なることに注意してください)。 - 元の型が bool の場合、値 false は結果の型のゼロに変換され、値 true は 1 に変換されます (結果の型が int の場合、これは整数昇格であって、整数変換ではないことに注意してください)。
- 結果の型が bool の場合、これはブーリアン変換 (後述) です。
- 結果の型が符号なしの場合、結果の値は x modulo 2n
[編集] 浮動小数点変換
浮動小数点型の prvalue は、任意の他の浮動小数点型の prvalue に変換できます。 変換が浮動小数点昇格の項目に掲載されている場合、それは昇格であって、変換ではありません。
- 元の値が結果の型で正確に表現できる場合、それは変更されません。
- 元の値が結果の型で表現可能な2つの値の間の場合、結果はその2つの値のどちらかです (どちらであるかは処理系定義です。 しかし IEEE 算術がサポートされている場合、丸めのデフォルトは最も近い値です)。
- そうでなければ、動作は未定義です。
[編集] 浮動小数点と整数の変換
- 浮動小数点型の prvalue は、任意の整数型の prvalue に変換できます。 小数部は切り捨てられます。 つまり、小数部は破棄されます。 値が結果の型に収まらない場合、動作は未定義です (結果の型が符号なしのときでも、モジュロ算術は適用されません)。 結果の型が bool の場合、これはブーリアン変換 (後述) です。
- 整数型または符号なし列挙型の prvalue は、任意の浮動小数点型の prvalue に変換できます。 値が正確に表現できない場合、最も近いより大きな表現可能な値と最も近いより小さな表現可能な値のどちらが選択されるかは処理系定義です (しかし IEEE 算術がサポートされている場合、丸めのデフォルトは最も近い値です)。 値が結果の型に収まらない場合、動作は未定義です。 元の型が bool の場合、値 false はゼロに変換され、値 true は 1 に変換されます。
[編集] ポインタ変換
- ヌルポインタ定数 (NULL を参照) は、任意のポインタ型に変換でき、結果はその型のヌルポインタ値です。 そのような変換 (ヌルポインタ変換と言います) は、 cv 修飾された型への変換を単一の変換として許可します。 つまり、数値変換と修飾子変換の組み合わせとはみなされません。
- 任意のオブジェクト型
T
(cv 修飾されていても構いません) へのポインタの prvalue は、 (同じに cv 修飾された) void へのポインタの prvalue に変換できます。 結果のポインタは元のポインタ値と同じメモリ内の位置���表します。 元のポインタがヌルポインタ値の場合、結果は結果の型のヌルポインタ値です。 - 派生クラス型 (cv 修飾されていても構いません) へのポインタの prvalue は、 (同じに cv 修飾された) その基底クラスへのポインタの prvalue に変換できます。 基底クラスがアクセス不可能または曖昧な場合、変換は ill-formed です (コンパイルできません)。 変換の結果は、その指している先のオブジェクト内の基底クラス部分オブジェクトへのポインタです。 ヌルポインタ値は結果の型のヌルポインタ値に変換されます。
[編集] メンバポインタ変換
- ヌルポインタ定数 (NULL を参照) は、任意のメンバへのポインタ型に変換でき、結果はその型のヌルポインタ値です。 そのような変換 (ヌルメンバポインタ変換と言います) は、 cv 修飾された型への変換を単一の変換として許可します。 つまり、数値変換と修飾子変換の組み合わせとはみなされません。
- 基底クラス
B
内の何らかの型T
のメンバへのポインタの prvalue は、その派生クラスD
内の同じ型T
のメンバへのポインタの prvalue に変換できます。B
がアクセス不可能、曖昧、D
の仮想基底、またはD
の何らかの中間仮想基底の基底である場合、変換は ill-formed です (コンパイルできません)。 結果のポインタはD
オブジェクトを用いて逆参照でき、そのD
オブジェクトのB
基底部分オブジェクト内のメンバにアクセスします。 ヌルポインタ値は結果の型のヌルポインタ値に変換されます。
[編集] ブーリアン変換
整数型、浮動小数点型、スコープなし列挙型、ポインタ型、メンバへのポインタ型の prvalue は、 bool 型の prvalue に変換できます。
値ゼロ (整数、浮動小数点、符号なし列挙の場合)、ヌルポインタ値、ヌルメンバポインタ値は false になります。 他のすべての値は true になります。
直接初期化の文脈では、 bool オブジェクトは std::nullptr_t 型の prvalue (nullptr を含みます) から初期化できます。 結果の値は false です。 しかし、これは暗黙の変換であるとはみなされません。 |
(C++11以上) |
[編集] 修飾子変換
「より多く」 cv 修飾されたとは、以下のことを意味します。
- 無修飾型へのポインタは
const
へのポインタに変換できます。 - 無修飾型へのポインタは
volatile
へのポインタに変換できます。 - 無修飾型へのポインタは
const volatile
へのポインタに変換できます。 -
const
型へのポインタはconst volatile
へのポインタに変換できます。 -
volatile
型へのポインタはconst volatile
へのポインタに変換できます。
- 無修飾型へのポインタは
多段ポインタの場合は、以下の制限が適用されます。 cv1
n 修飾された T
への cv1
n-1 修飾されたポインタへの ... への cv1
1 修飾されたポインタへの cv1
0 修飾されたポインタである多段ポインタ P1
は、以下の場合にのみ、 cv2
n 修飾された T
への cv2
n-1 修飾されたポインタへの ... への cv2
1 修飾されたポインタへの cv2
0 修飾されたポインタである多段ポインタ P2
に変換できます。
- 両方のポインタについて、段数
n
が同じである。 - P1 の (0段目以外の) 何らかの段の cv1
k 修飾に const が存在する場合、 P2 の同じ段の cv2
k に const が存在する。 - P1 の (0段目以外の) 何らかの段の cv1
k 修飾に volatile が存在する場合、 P2 の同じ段の cv2
k に volatile が存在する。 - 何らかの段
k
において、P2
がP1
より多く cv 修飾されている場合、P2
の k 段目までの (0段目以外の) すべての段 cv2
1, cv2
2 ... cv2
k において、 const が存在しなければなりません。 - 同じルールが多段メンバへのポインタおよびオブジェクトへのポインタとメンバへのポインタの多段混合ポインタにも適用されます。
- 両方のポインタについて、段数
|
(C++14以上) |
- 0段目は多段でない修飾子変換に対するルールによって扱われます。
char** p = 0; const char** p1 = p; // エラー、2段目はより多く cv 修飾されていますが、1段目は const ではありません。 const char* const * p2 = p; // OK、2段目はより多く cv 修飾され、1段目は const が追加されています。 volatile char * const * p3 = p; // OK、2段目はより多く cv 修飾され、1段目は const が追加されています。 volatile const char* const* p4 = p2; // OK、2段目はより多く cv 修飾され、1段目はすでに const でした。 double *a[2][3]; double const * const (*ap)[3] = a; // C++14 以上では OK。
C 言語では、 const/volatile を追加できるのは最初の段のみであることに注意してください。
char** p = 0; char * const* p1 = p; // C でも C++ でも OK。 const char* const * p2 = p; // C ではエラー、 C++ では OK。
関数ポインタ変換
void (*p)(); void (**pp)() noexcept = &p; // エラー、 noexcept 関数へのポインタには変換できません。 struct S { typedef void (*p)(); operator p(); }; void (*q)() noexcept = S(); // エラー、 noexcept 関数へのポインタには変換できません。 |
(C++17以上) |
[編集] 安全な bool の問題
C++11 で explicit 変換関数が導入されるまで、ブーリアンの文脈 (例えば if(obj) { ... }) で使用可能であるべきクラスの設計は、ある問題を提示しました。 T::operator bool() const; のようなユーザ定義変換関数を与えると、暗黙の変換シーケンスはその関数呼び出しの後にもうひとつ追加の標準変換を許容しました。 つまり、結果の bool を int に変換することができ、 obj << 1; や int i = obj; のようなコードを許容しました。
これに対する初期の解決方法のひとつは std::basic_ios で見られます。 std::basic_ios は operator! と operator void*(C++11未満) を定義しています。 これにより if(std::cin) {...} のようなコードは void* が bool に変換可能なためコンパイルできますが、 int n = std::cout; のようなコードは void* が int に変換可能でないためコンパイルできません。 これはまだ delete std::cout; のようなナンセンスなコードを許容するので、 多くの C++11 前のサードパーティライブラリは、安全な bool のイディオムとして知られる、よりエレガントな解決方法を用いて設計されました。
explicit bool 変換は安全な bool の問題を解決するためにも使用できます。 explicit operator bool() const { ... } |
(C++11以上) |
[編集] 注釈
- ↑ これは算術が2の補数の場合にのみ適用されます。 2の補数算術は固定幅の整数型に対してのみ要求されます。 しかし、現在のところ、 C++ コンパイラが存在するすべてのプラットフォームは2の補数算術を使用していることに注意してください。
[編集] 欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
DR | 適用先 | 発行時の動作 | 正しい動作 |
---|---|---|---|
CWG 330 | C++14 | conversion from double * const (*p)[3] to double const * const (*p)[3] invalid | conversion valid |
CWG 616 | C++11 | lvalue to rvalue conversion of any uninitialized object was always UB | indeterminate unsigned char is allowed |
CWG 1423 | C++11 | std::nullptr_t is convertible to bool in both direct- and copy-initialization | direct-initialization only |
CWG 1781 | C++11 | std::nullptr_t to bool is considered a implicit conversion even though it is only valid for direct-initialization | no longer considered an implicit conversion |
[編集] 関連項目
暗黙の変換 の C言語リファレンス
|