リテラル文字列の処理は、Visual C++ 2010 では C++ マネージ拡張から変更されています。
C++ マネージ拡張言語デザインでは、マネージ リテラル文字列はリテラル文字列に S を前置することで示していました。 この例を次に示します。
String *ps1 = "hello";
String *ps2 = S"goodbye";
次の CIL 表現で ildasm を通じて示しているとおり、この 2 つの初期化の間のパフォーマンス オーバーヘッドは重要です。
// String *ps1 = "hello";
ldsflda valuetype $ArrayType$0xd61117dd
modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier)
'?A0xbdde7aca.unnamed-global-0'
newobj instance void [mscorlib]System.String::.ctor(int8*)
stloc.0
// String *ps2 = S"goodbye";
ldstr "goodbye"
stloc.0
リテラル文字列に S を前置することを覚えておく (または学習する) だけで済むため非常に助かります。 新しい構文では、リテラル文字列の処理は透過的に行われ、利用状況に応じて決定されます。 S を指定する必要はありません。
コンパイラにいずれの解釈であるかを明示的に指示する必要がある場合はどうでしょうか。 この場合、明示的なキャストを適用します。 この例を次に示します。
f( safe_cast<String^>("ABC") );
さらに、リテラル文字列は、String を標準変換ではなく単純変換と突き合わせるようになりました。 これは何でもないことのように聞こえますが、競合する仮パラメーターと同様、String および const char* を含む一連のオーバーロードされた関数の解決を変更します。 const char* インスタンスに解決されていた解決が、あいまいとしてフラグされるようになりました。 この例を次に示します。
ref struct R {
void f(const char*);
void f(String^);
};
int main () {
R r;
// old syntax: f( const char* );
// new syntax: error: ambiguous
r.f("ABC");
}
相違があるのはなぜでしょうか。 このプログラム内に f という名前のインスタンスが 1 つ以上存在するため、関数のオーバーロードの解決アルゴリズムを呼び出しに適用する必要があります。 オーバーロード関数の正式な解決は 3 つの処理で構成されています。
候補関数のコレクション。 候補関数は、呼び出している関数の名前が構文的に一致するスコープ内のメソッドです。 たとえば、f() は R のインスタンスを通じて呼び出されるため、R (またはその基本クラス階層構造) のメンバーでない f という名前の関数は候補関数ではありません。 この例では、2 つの候補関数が使用されています。 f という名前の R のメンバー関数が 2 つ存在します。 候補関数のセットが空の場合、このフェーズでの呼び出しは失敗します。
候補関数の中の実行可能関数のセット。 実行可能関数は、複数の引数とその型を持つ呼び出しに指定された引数で呼び出すことができる関数です。 この例では、2 つの候補関数は実行可能関数でもあります。 実行可能関数のセットが空の場合、このフェーズでの呼び出しは失敗します。
呼び出しの一致が最優先の関数を選択します。 これは、実行可能関数パラメーターの型に引数を変換するために適用する変換をランク付けして行います。 これは、パラメーターが 1 つだけ存在する関数の場合は比較的容易ですが、複数のパラメーターが存在する場合はより複雑になります。 最優先の一致が存在しない場合、このフェーズでの呼び出しは失敗します。 つまり、実引数の型を仮パラメーター型に変換する必要がある変換が等しく適切である場合、 この呼び出しはあいまいとしてフラグされます。
マネージ拡張では、この呼び出しの解決によって const char* インスタンスが最優先の一致として呼び出されます。 新しい構文では、"abc" を const char* と String^ に突き合わせるために必要な変換は同等 (つまり、等しく適切) で、この呼び出しは不適切 (つまり、あいまい) としてフラグされます。
これにより、2 つの疑問が生じます。
実引数 "abc" の型は何でしょうか。
一方の型変換が他方の型変換より優先されると判断するアルゴリズムはどのようなものでしょうか。
リテラル文字列 "abc" の型は const char[4] です。リテラル文字列の末尾には暗黙の null 終端文字があります。
一方の型変換が他方の型変換より優先されると判断するアルゴリズムには、型変換の階層構造への配置が含まれます。 階層構造について理解するところでは、これらの変換はすべて暗黙的に行われます。 明示的なキャスト表記を使用すると、階層構造がオーバーライドされます。これは、かっこが式の通常の演算子をオーバーライドするのと同様です。
厳密な一致が最優先されます。 意外にも、引数を厳密な一致にするには、パラメーター型が厳密に一致している必要はなく、ある程度似ているだけで十分です。 これは、この例で何が行われているかを理解し、言語がどのように変更されたかを理解するうえでの鍵となります。
上位変換は標準変換より優先されます。 たとえば、short int の int への上位変換は、int の double への変換より優先されます。
標準変換はボックス化変換より優先されます。 たとえば、int の double への変換は、int の Object へのボックス化変換より優先されます。
ボックス化変換は暗黙のユーザー定義の変換より優先されます。 たとえば、int の Object へのボックス化は、SmallInt 値クラスの変換演算子の適用より優先されます。
暗黙のユーザー定義の変換は変換なしより優先されます。 暗黙のユーザー定義の変換がない場合、アルゴリズムはエラーになります (ただし、正式なシグネチャではここにパラメーター配列または省略記号が格納される場合があります)。
ところで、厳密な一致が厳密に一致している必要がないとはいったいどういうことでしょうか。 たとえば、const char[4] は const char* または String^ のいずれとも厳密に一致していません。さらに、この例におけるあいまいさは 2 つの矛盾する厳密な一致の間に存在します。
厳密な一致には、偶然にも、複数の単純変換も含まれます。 適用できる ISO-C++ には 4 種類の単純変換があり、厳密な一致と見なされます。 これらは、左辺値変換と呼ばれています。 また、4 番目の型は限定変換と呼ばれています。 この 3 種類の左辺値変換は、限定変換を必要とする厳密な一致より優先される厳密な一致として扱われます。
左辺値変換のフォームの 1 つに、ネイティブ配列からポインターへの変換があります。 これは、const char[4] の const char* との突き合わせに含まれています。 このため、f("abc") を f(const char*) と突き合わせると、厳密な一致になります。 この言語の初期の段階では、これが最優先の一致でした。
このため、コンパイラが呼び出しをあいまいとしてフラグするには、const char[4] の String^ への変換も単純変換を通じて厳密な一致とする必要があります。 この変更は、新しい言語バージョンで導入されました。 この呼び出しがあいまいとしてフラグされるのはこのためです。
参照
参照
System::String Handling in Visual C++