Standard
ECMA-372
C++/CLI 言語仕様書
西暦2005年12月 初版
ナビゲーション リンクのスキップ

14.型変換


14.1 変換順序

 ボックス化変換とパラメータ配列変換を協調的に追加するために、標準 C++ の §13.3.3.2 は次のように改訂されます。
 暗黙の型変換順序(§13.3.3.1で定義されているような)の基本的な形を比較する時、
  • 標準型変換順序(13.3.3.1.1)は、ボックス化変換順序、ユーザー定義変換順序、パラメータ配列変換順序、ないし、省略変換順序よりも優先される変換順序であり、
  • ボックス化変換順序はユーザー定義変換順序、パラメータ配列変換順序、ないし、省略変換順序より優先される変換順序であり、
  • ユーザー定義変換順序(13.3.3.1.2)はパラメータ配列変換順序、ないし、省略変換順序(13.3.3.1.3)より優先される変換順序であり、
  • パラメータ配列変換順序は省略変換順序(13.3.3.1.3)より優先される変換順序です。

14.2 標準変換

 標準C++ の標準変換は C++/CLI に適用されます。C++/CLI は次のような標準変換を持ちます。

14.2.1 ハンドル型変換

 ハンドル変換は標準C++ (§4.10)で定義されているようなポインタ変換と似ています。 ハンドル変換の追加を協調的に行うために、標準C++ §13.3.3.1.1 ”標準変換順序”の「変換」テーブル9、は、§18.2に示されるように、”ハンドル変換”の行が追加されています。
 D が型である ”cv D へのハンドル”型の右辺値は B が D の基底クラスである”cv Bへのハンドル”型の右辺値に変換することができます。その変換の結果は同じオブジェクトへのハンドルです。
 型 void^ は不正であるので、それへのハンドル変換は存在しません。
 n が同じ(共にCLI配列のランク)であり、S^ から T^ へのハンドル変換が提供されている array<S^,n> 型のハンドルは array<T^,n> 型のハンドルへのハンドル変換を持ちます。 その様な変換は array<S^,n> 型から System::Array^ 型への変換より優先です。 この関係は array covariance(配列共変性) として知られています。 配列コバリアンスは配列の要素型の基底クラスを参照する変数を許すことができるので、ハンドル型配列の要素への代入は CLI によって実行時チェックが実行されることを含んでいます。 (CLI パーティションIII、§4.26 と §4.27 を参照のこと)。 実行時チェックは配列要素に代入される値が許可された型のものであることを保証します。 配列コバリアンスは明確に言えば値型の CLI 配列に拡張していません。例えば、array<int> を array<Object^> として扱うような変換は存在しません。
 ハンドルは条件演算子の第一オペランドとして使うことができます。
 ヌル値定数は任意のハンドル型に変換することができます。 その結果はその型の null value (ヌル値) を持つハンドルであり、CLI ヒープ・ベース・オブジェクトへのハンドルである全ての他の値と区別可能です。 これをサポートするために、標準C++ は次のように変更されます。
§4/2: [注記:―― if 命令文やイテレーション命令(6.4, 6.5)の条件で使われる時。 もし、条件がハンドル型であれば、そして、ハンドルから bool への変換が可能でなかったら、目的の型はハンドル型であり、そうでなければ、目的の型は bool です。... ]
§5.16/1: 最初の演算は暗黙のうちに bool に変換されます。(第4節)もし、その変換が不正であり、その演算がハンドル型か値クラス拘束によって拘束されていないジェネリック型パラメータによって与えられた型であれば、 その演算はヌル値とテストされ、もし、null でなければ true を、null であれば false を返します。 そうでなければ、もし、bool への変換が不正であり、演算はハンドル型や値クラス拘束によって拘束されていないジェネリック型パラメータによって与えられた型でなければ、そのプログラムは不正です。
§6.4/4: switch 命令文以外の命令文で初期化された宣言である condition (条件) の値は暗黙のうちに bool 型に変換された宣言された変数の値です。 もし、その変換が不正であれば、そのプログラムは不正です。switch 命令文で初期化宣言された条件の値は、もし、それが整数や列挙型を持っていれば、宣言された変数の値であり、さもなければ、暗黙のうちに整数型や列挙型に変換された変数の値となります。 演算の値が演算である条件の値は暗黙のうちに switch 以外の命令文では bool に変換されます。 もし、その変換が不正であれば、そのプログラムは不正です。条件の値は使い方が明らかでなければ単に”条件”として参照されるでしょう。 演算である条件の値は、switch 以外の命令で暗黙のうちに bool に変換される、その演算の値です。 もしその変換が不正であり、その演算はハンドル型か値クラス拘束によって拘束されていないジェネリック型パラメータによって与えられた型であれば、その演算はヌル値とテストされ、もし、null でなければ true を、null であれば false を返します。 そうでなければ、もし、bool への変換が不正で、その演算がハンドル型でも値クラス拘束によって拘束されていないジェネリック型パラメータによって与えられた型でもなければ、そのプログラムは不正です。 [注意:もし、bool へのなんの変換もなく、宣言された変数や演算がハンドル型でなければ、ハンドル型への変換は考慮されません。]
§6.5.2/1: その演算は暗黙のうちに bool に変換されます。もし、それが可能でなければ、そして、その演算がハンドル型か値クラス拘束によって拘束されていないジェネリック型パラメータによって与えられた型であれば、それは null とテストされます。 もし、bool へのなんの変換も存在せず、その演算がハンドル型でも値クラス拘束によって拘束されていないジェネリック型パラメータによって与えられた型でもなければ、そのプログラムは不正です。

14.2.1.1 ランキング・ハンドル変換
 追加の標準変換として、C++/CLI は、ある変換がもう一つの変換よりもよいかどうかを決定するためのさらなるランキングを要請することができるハンドル変換のみを追加します。標準C++ §13.3.3.2/4 のルールに加えて、以下のルールが適用されます。
  • もし、クラス B は直接的、非直接的にクラス A から派生しており、クラス C は直接的、非直接的に B から派生していると、
    • C^ と B^ の変換は C^ と A^ の変換より優先です。
    • B^ と A^ の変換は C^ と A^ の変換より優先です。

14.2.2 ポインタ型変換

 標準C++(§4.10/1)中の null pointer constant (ヌル・ポインタ定数) の定義は以下のように拡張されます。
”ヌル・ポインタ定数は0に評価される整数型の整数定数演算右辺値か、ヌル値定数 nullptr のどちらかです。
[注意:これの言外の意味はヌル値定数は任意のポインタ型に変換されることができると言うことです。]

 以下の変換ルールが内部ポインタに適用されます。
 interior_ptr<T1> から interior_ptr<T2> への変換は、T1* から T2* への変換が許されている場合、そして、ただその場合にのみ、許されます。
 ある型が厳密に interior_ptr<T1> である型同士の変換において、内部ポインタは二つの例外を除いて、厳密に”cv T1 へのポインタ”であるかのように振る舞います。
  • ”cv T1 へのポインタ”以外の型への変換は許されません。特に、interior_ptr<T> から T* への変換が許されていません。
  • ヌル・ポインタ定数から interior_ptr<T> への変換が許されていません。(しかし、nullptr からの変換は許されます。)
[例:
array<int>^ arr = gcnew array<int>(100);
interior_ptr<int> ipi = &arr[0];
int* p = ipi;         // エラー;interior から 非 interior への変換はありません
int k=10;
ipi = &k              // OK; k は自動変数
ipi = 0;              // エラー;代わりに nullptr を使わなければならない
ipi = nullptr;        // OK
ipi = p;              // OK
if (ipi) { ... }      // OK

 以下の変換ルールがピンニング・ポインタに適用されます。
 pin_ptr<T1> から pin_ptr<T2> への変換は、T1* から T2* への変換が許されている場合、そして、ただその場合にのみ、許されています。
 厳密にある型が cv pin_ptr<T> である型間の変換において、ピンニング・ポインタは、ヌル・ポインタ定数から pin_ptr<T> への変換が許されていないという例外を持って、厳密にそれが”cv T へのポインタ”であるかのように振る舞います。 [注意:特に、pin_ptr<T> から T* への変換は、標準変換として許されています。]
[例:
array<int>^ arr = gcnew array<int>(100);
pin_ptr<int> ppi = &arr[0];
int* p = ppi;           // OK
int k = 10;
ppi = &k;               // OK; k は自動変数
ppi = 0;                // エラー;代わりに nullptr を使わなければならない
ppi = nullptr;          // OK
pin_ptr<int> ppi2 = p;  // OK


14.2.3 左辺値変換

 次のそれぞれに標準変換があります。 "型 T の cv-qualified 左辺値" を "型 T の cv-qualified gc左辺値"に、そして、"型 T の cv-qualified gc左辺値" を "型 T の cv-qualified 右辺値"。 もし、cv-qualified 左辺値は与えられたコンテキスト中で右辺値に変換しないであれば、右辺値に変換する gc-lvalue は不正な形式です。 [根拠:CLIヒープ上の整数値にネイティブ参照を拘束する時、gc左辺値から右辺値への変換は型安全のロスを引き起こします。]

14.2.4 整数促進

 拡張整数型を協調的に追加するために、標準C++ (§4.5/1)は以下のように変更されます。
char, signed char, unsigned char, short int、ないし、unsigned short intその整数変換ランク(4.13)が int と unsigned int のランクよりも小さい整数型の右辺値は、 もし、int が元となる型の全ての値を表現できるのであれば、int 型の右辺値に変換されることが可能です。 そうでなければ、元となる右辺値は unsigned int 型の右辺値に変換されることができます。
 そして、標準C++ に次の新しい節 4.13 が追加されます。
4.13 整数変換ランク
 全ての整数型は以下に定義する integer conversion rank (整数変換ランク) を持ちます。
・例えそれらが同じ表現を持っていたとしても、同じランクを持つ二つの符号付き整数型が存在するべきではありません。
・符号付き整数型のランクはより低い精度の任意の符号付き整数型のランクよりも大きくなるべきです。
・long long int のランクは long int のランクより大きいべきであり、long int のランクは int のランクより大きいべきであり、 int のランクは short int のランクより大きいべきであり、short int のランクは signed char のランクより大きいべきです。
・任意の符号無し整数値型のランクは、もしあれば、対応する符号付き整数型のランクと同じであるべきです。
・任意の標準整数型のランクは同じ幅を持つ任意の拡張整数型のランクより大きいべきです。
・char のランクは signed char と unsigned char のランクと同じであるべきです。
・bool のランクは他の全ての標準整数型のランクよりも少なくあるべきです。
・任意の列挙型のランクはその背景型(7.2)のランクと等しいべきです。
・任意の拡張符号付き整数型のランクは同じ精度を持つもう一つの拡張符号付き整数型との比較は実装依存ですが、依然、整数変換ランクを決定するための他のルールに従います。
・全ての整数型 T1, T2、そして、T3 について、もし、T1 が T2 より大きなランクを持ち、T2 が T3 より大きなランクを持つのであれば、その時、T1 は T3 より大きなランクを持ちます。
[注意:整数変換ランクは整数促進(4.5)と通常算術変換(5)の定義中で使われます。]
 型 long long int と unsigned long long int を協調的に追加するために、標準C++ (§4.5/2)は次のように変更されます。
”型 wchar_t (3.9.1)ないし、型 System::Char の右辺値はその背景型の全ての値を表現することができる次の型の最初の右辺値に変換することができます。 int, unsigned int, long, ないし、unsigned long, long long int, ないし、unsigned long long int。 列挙型(7.2)の右辺値は列挙の全ての値を表現できる次の型の最初の右辺値に変換されることができます。 int, unsigned int, long, ないし、unsigned long, long long int, ないし、unsigned long long int。”

14.2.5 文字列リテラル変換

 <narrow-string-literal-type> 型の右辺値は二つの型:System::String^ か、"const char n 個の配列"の一方に変換されることができます。 ある <narrow-string-literal-type> が System::String^ に変換される時、その結果は CLI 文字列リテラル(§34.4.1 )として扱われます。 ある <narrow-string-literal-type> が配列に変換される時、n は(標準C++ §2.13.4/5で定義されている)文字列のサイズであり、その配列は静的保存期間を持ち、その配列は与えられた文字列で初期化されます。 <narrow-string-literal-type> から System::String^ への変換は <narrow-string-literal-type> から "const char n 個の配列"への変換よりも優先されます。
 <wide-string-literal-type> 型の右辺値は二つの型:System::String^ か、"const wchar_t n 個の配列"の一つに変換されることができます。 ある <wide-string-literal-type> が System::String^ に変換される時、その結果は CLI 文字列リテラル(§34.4.1 )として扱われます。 ある <wide-string-literal-type> が配列に変換される時、n は(標準C++ §2.13.4/5で定義されている)文字列のサイズであり、その配列は静的保存期間を持ち、その配列は与えられた文字列で初期化されます。 <wide-string-literal-type> から System::String^ への変換は <wide-string-literal-type> から "const char n 個の配列"への変換よりも優先されます。
 配列要素演算子の存在による変換は、§15.3.1 を参照。単項 * 演算子は§15.4.1.2 を参照。 二項 -> 演算子は§15.3.4 を、そして、二項 + 演算子は、§.15.6.3 を参照してください。
 parameter-declaration-clause (パラメータ宣言節) が省略で終わる関数の場合を考えると、関数は省略に対応した引数として文字列リテラルで呼び出されます。 もし、文字列リテラルが narrow string literal であれば、それは n 個の char 配列に変換され、もし、wide string literal であれば、それは n 個の wchar_t 配列に変換されます。

14.2.6 ボックス化変換

 ボックス化変換は CLI ヒープ上への新しいオブジェクトの生成を含んでいます。 ボックス化変換はポインタを除く値型のインスタンスにのみ適用されるべきです。任意の与えられた型 V に、変換は V^ を結果とします。 [注意:そのほかのいくつかの CLIベース言語のボックス化は直接 V から Object^ に向かいます。これは C++/CLI ではハンドル変換を従えたボックス化変換を通して得られます。] 値型演算は cv-qualified であり得ますが、ボックス化された値型の結果はそうではありません。
 ボックス化変換の追加を協調的に行うために、標準C++の §13.3.3.1.1「標準変換シーケンス」、"変換"表9は§18.3 で示されるように、"ボックス化変換"の行が追加されます。 [例:そのテーブル中のボックス化変換の位置づけは "narrowing" 変換とボックス化の間の選択が与えられると、ボックス化が優先されることを意味していることに注意してください。 次のように与えられると、
void F(float f) {
    Console::WriteLine("F(float)");
}
void F(Object^ o) {
    Console::WriteLine("F(Object^)");
}
int main() {
    F(3.14);
}
 出力は "F(Object^)" です。]

 ボックス化変換はユーザーによって上書きすることはできません。それは実装によって予約されています。
 ボックス化変換はユーザー定義変換(標準C++ §13.3.3.1.2)として厳密に同じ演算子順序に従います。ボックス化変換はユーザー定義変換の前に考慮され、 ボックス化変換順序は決してユーザー定義変換に含まれることはありません。 言い換えれば、ボックス化変換かユーザー定義変換かの選択が与えられたら、ボックス化変換が選択されます。 故に、標準C++ の §13.3.3.2 は、§14.1 のように改訂されます。
[注意:ボックス化変換と同じ変換を実行するユーザー定義変換演算子を書くことは可能です。 コンパイラはそのボックス化のコンテキストでユーザー定義変換を呼ぶことはないでしょうが、プログラマは明示的な変換関数構文を使って、そのユーザー定義変換を呼ぶことが可能です。]

 メタデータの詳細は、§34.4.2 を参照のこと。

14.3 暗黙の型変換


14.3.1 暗黙の定数表現変換

 次の暗黙の定数演算変換は許されています。
  • null 値定数は任意のポインタ型に変換されうる。
  • null 値定数は任意のハンドル型に変換されうる。

14.3.2 ユーザー定義の暗黙の型変換


14.3.3 ブーリアン等価性

 もし、bool が System::Boolean にマップされているかいないかにかかわらず、bool 型の右辺値は System::Boolean の右辺値に変換でき、System::Boolean 型の右辺値は bool 型の右辺値に変換できます。

14.4 明示的な型変換

 次の明示的な変換は許されています。
  • null 値定数は任意のポインタ型に変換されうる。
  • null 値定数は任意のハンドル型に変換されうる。

14.5 ユーザー定義の型変換

 ジェネリック変換関数は許されています。[注意:しかしながら、多重定義解決の後にジェネリック拘束検証を行う必要性は、便利なジェネリック変換を記述することを困難にしています。テンプレート変換関数は通常もっと便利です。]

14.5.1 コンストラクタ

 explicit キーワードは ref クラスか値クラスのコンストラクタに許されていますが、何の効果もありません。これらのクラスのコンストラクタは変換や型変換(§13.3 を参照)に使われることはありません。

14.5.2 明示的型変換関数

 C++/CLI は変換関数に explicit キーワードを許します。故に、標準C++ §7.1.2 は以下のように変更されます。
"explicit 指定子はクラス宣言中のコンストラクタの宣言中か、クラス宣言中の変換関数の宣言でのみ使われるべきでしょう。12.3.1 と、12.3.2 を参照のこと。"
 explicit キーワードで宣言された変換関数は explicit conversion function (明示的変換関数) として知られています。explicit キーワード無しで宣言された変換関数は(たとえば、標準C++中の全ての変換関数)は implicit conversion function (暗黙的変換関数) として知られています。
 明示的コンストラクタ同様、明示的変換関数は直接初期化構文(標準C++ §8.5)とキャスト(標準C++ §5.2.9, §5.4)によってのみ呼び出されることが可能です。
 型は同じ変換を実行できる暗黙的変換関数と明示的変換関数を含むべきではありません。それらの一つのみが許されます。
 同じ変換を実行できる明示的変換コンストラクタと変換関数の双方を持つクラスを書くことは可能です。この場合、明示的変換関数が優先されます。

14.5.3 静的型変換関数

 C++/CLI は明示的、暗黙的の双方で static である変換関数を許しています。変換関数は名前空間スコープを持つべきではありません。 静的変換関数はただ一つのパラメータを持つべきであり、そのパラメータは変換される型です。 (非静的メンバ変換関数はパラメータを持つべきではありません。) 静的、非静的変換関数の双方共に返値を指定するべきではありません。
 ソースとなる型(パラメータ型)や標的型(type-specifier-seq)の双方について、T が含まれているクラスの型だとすると、T、T^、T&、T%、T^%、ないし、T^& であることが要請されています。 (T* は変換はポインタを通して名前検索しないので、許されていません。)
 暗黙の変換は今や一つ以上の場所、ソース演算の型のスコープと全ての可能な標的型のスコープ、で見つけることができます。 もし、多重定義解決が同じ変換を実行することができる変換関数(と可能な変換コンストラクタ)のセットを結果とした場合には、プログラムは曖昧であり、不正です。

14.6 パラメータ配列の型変換

 パラメータ配列変換シーケンスは、最後の引数にパラメータ配列を取る関数が多重定義解決で選ばれた時、発生します。そのような多重定義は C スタイルの可変長引数関数が好まれ、それ以外の多重定義は好まれません。
 パラメータ配列多重定義は多重定義解決で選択されます。多重定義解決のために、コンパイラはパラメータ配列引数を n 個の、n は関数呼び出しでの引数の数に当たる、CLI 配列要素型に置き換えることでパラメータ配列関数のためのシグネイチャを作成します。 これら合成されたシグネイチャは他の合成されていないシグネイチャよりも高いコストを持ち、省略で終わる parameter-declaration-clause (パラメータ宣言節) の関数よりは低いコストとなります。 [注意:これは標準C++(§13.3.3)のテンプレート関数と非テンプレート関数のタイ・ブレーク・ルールと同じです。]
 例えば、関数呼び出し f(var1, var2, ..., varm, val1, val2, ..., valn)
void f(T1 arg1, T2 arg2, ..., Tm argm, ... array<T>^ arr)
は次のように置き換えられます。
void f(T1 arg1, T2 arg2, ..., Tm argm, T t1, T t2, ..., T tn)
 多重定義解決は標準C++のルールによって合成シグネイチャを含むセットでもって実行されます。 もし、多重定義解決が C スタイルの可変数引数変換を選べば、それは合成されたシグネイチャのいずれも選ばれていないことを意味しています。
 もし、多重定義解決が合成シグネイチャの一つを選択したら、その呼び出しを満たす各々の引数を必要とする変換シーケンスが実行されます。 合成パラメータ配列引数のために、コンパイラは長さ n の CLI 配列を生成し、変換された値でその要素を初期化します。 そして、生成されたパラメータ配列を引数に関数呼び出しが行われます。
[注意:ユーザー定義変換はパラメータ配列変換より優先されます。
ref class A {};
ref class B {
public:
    static operator A^(B^ b) { return gcnew A; }
};
void F(A^ a) { Console::WriteLine("A^"); }

int main() {
    B^ b = gcnew B;
    F(b);
}
 このプログラムは A^ を表示します。]

14.7 名前付け変換

 コンパイルの間、変換関数の名前はその関数のためにソースコード中で使われているC++識別子です。 例えば、A から B への変換関数は A か B の静的メンバ関数に、または、A のインスタンス関数、operator B() になることができます。[例:
public value struct Decimal {
    ...
    static operator Decimal(int value);
    static explicit operator double(Decimal value);

    explicit operator float();
};


 ref クラス、値クラス、ないし、インターフェイス・クラスに名前 op_Implicit や op_Explicit という名前を使ってメンバ関数を宣言、定義するプログラムは不正です。 プログラムは決して直接的にこれらの名前を使うべきではありません。
 演算子関数はまた、それぞれ CLS-compliant、ないし、C++-dependent です。
 変換関数は以下の条件全てが発生した時、CLS-compliant (CLS互換) です。
  • 変換関数は ref クラス、ないし、値クラスの静的メンバ関数である。
  • もし、値クラスが変換関数の標的値かパラメータであれば、値クラスはポインタ、ないし、ハンドルで渡されるたり、参照で渡されるべきではない。
  • もし、ref クラスが演算子関数の標的値、ないし、パラメータであれば、ref クラスはハンドルで渡されるべきである。ハンドルは参照で渡されるべきではない。
 もし、変換関数がこれらの条件に適合しなければ、それは C++-dependent (C++依存) です。

文責:翻訳 => ヽ(゚∀。)ノうぇね