文字列型とオーバーロード

投稿者:: Yusuke Konno

概要: 文字列型のオーバーロードされた関数・手続きがある場合、どれが呼ばれるかについて解説します。

Delphi 2009ではUnicodeStringの新設、AnsiString型の拡張によるコードページ指定可能なAnsiString型の登場などによって、文字列型の種類が一気に増加しました。それは、文字列処理ルーチンが受け取る引数の型の種類が増えたことを意味しています。本稿では文字列型とオーバーロードについての解説を行います。

    代入互換性とオーバーロード

    代入互換性の話

文字列型は大別するとUnicodeString、AnsiString(UTF8String、RawByteString含む)、WideString、UCS4Stringの4つに分けられます。UCS4Stringを除き、これらは相互に代入互換性があります。

代入互換性があると言うことは、異なる型に対しても代入が出来てしまうことを意味しますから、適切な引数の型を用いたオーバーロード関数や手続きが存在しない場合、どの関数・手続きが呼ばれるかが問題となります。なぜなら、呼ばれる関数・手続きによっては暗黙の文字列変換が発生し、意図しない結果を招くことがあるからです。

もし、引数として渡す変数と同じ型のオーバーロード関数や手続きがある場合、話は簡単です。なぜなら、同じ型のものが呼ばれるからです。この場合は何も問題なく処理が行われるでしょう。

ところが、適切な型が無い場合は代入可能なものが呼ばれます。この時、場合によって暗黙の文字列変換が発生してしまうことになります。また、型によっては呼び出すべきオーバーロード関数や手続きが曖昧になってしまい、DCCエラーE2251が発生する可能性があります。

    UnicodeStringを引数に渡す場合

UnicodeStringの場合、呼び出しの優先度は以下の通りです。

UnicodeString > WideString > AnsiString(CodePage) = AnsiString

もし、UnicodeStringとWideStringのオーバーロードバージョンがない場合で、AnsiString(CodePage)とAnsiStringや、複数のAnsiString(CodePage)のオーバーロードバージョンがある場合は、DCCエラー2251が発生します。

以下のコードでは引数に渡す型がUnicodeStringであるのに対して、オーバーロードされている手続きの引数はAnsiStringとAnsiString(CodePage)だけです。この場合DCCエラー2251が発生し、コンパイルに失敗します。

procedure ShowMsg(S: AnsiString); overload;
begin
  ShowMessage('AnsiString');
end;

procedure ShowMsg(S: UTF8String); overload;
begin
  ShowMessage('UTF-8');
end;

{略}

var
  S: String;
begin
  S := 'abc';
  ShowMsg(S);//DCCエラー2251
end;

    WideStringを引数に渡す場合

WideStringの場合、UnicodeStringとほとんど同じです。

WideString > UnicodeString > AnsiString(CodePage) = AnsiString

もし、UnicodeStringとWideStringのオーバーロードバージョンがない場合で、AnsiString(CodePage)とAnsiStringや、複数のAnsiString(CodePage)のオーバーロードバージョンがある場合は、DCCエラー2251が発生します。

以下のコードでは引数に渡す型がWideStringであるのに対して、オーバーロードされている手続きの引数はAnsiStringとAnsiString(CodePage)だけです。この場合DCCエラー2251が発生し、コンパイルに失敗します。

procedure ShowMsg(S: AnsiString); overload;
begin
  ShowMessage('AnsiString');
end;

procedure ShowMsg(S: UTF8String); overload;
begin
  ShowMessage('UTF-8');
end;

{略}

var
  S: WideString;
begin
  S := 'abc';
  ShowMsg(S);//DCCエラー2251
end;

    AnsiStringを引数に渡す場合

コードページ指定のないAnsiStringの場合、呼び出しの優先度は以下の通りです。

AnsiString > AnsiString(CodePage) > UnicodeString > WideString

もしコードページ指定のないAnsiStringが無い場合で、複数のAnsiString(CodePage)のオーバーロードバージョンがある場合は、DCCエラー2251が発生します。

以下のコードでは引数に渡す型がAnsiStringであるのに対して、オーバーロードされている手続きの引数は複数のAnsiString(CodePage)だけです。この場合DCCエラー2251が発生し、コンパイルに失敗します。

procedure ShowMsg(S: UTF8String); overload;
begin
  ShowMessage('UTF-8');
end;

procedure ShowMsg(S: RawByteString); overload;
begin
  ShowMessage('RawByteString');
end;

{略}

var
  S: AnsiString;
begin
  S := 'abc';
  ShowMsg(S);//DCCエラー2251
end;

    AnsiString(CodePage) を引数に渡す場合

コードページ指定のあるAnsiStringの場合、呼び出しの優先度は以下の通りです。

AnsiString(CodePage) > AnsiString > UnicodeString > WideString

もし適切なコードページのオーバーロードバージョンがない場合で、複数のAnsiString(CodePage)のオーバーロードバージョンがある場合は、DCCエラー2251が発生します。

以下のコードでは引数に渡す型がAnsiString(20932)であるのに対して、オーバーロードされている手続きの引数は複数のUTF8String=AnsiString(65001)とRawByteString=AnsiString(65535)だけです。この場合DCCエラー2251が発生し、コンパイルに失敗します。

procedure ShowMsg(S: UTF8String); overload;
begin
  ShowMessage('UTF-8');
end;

procedure ShowMsg(S: RawByteString); overload;
begin
  ShowMessage('RawByteString');
end;

type
  EUC = type AnsiString(20932);
var
  S: EUC;
begin
  S := 'abc';
  ShowMsg(S);//DCCエラー2251
end;

    複数の文字列型を引数に取る場合

    ミスしやすい代表例:Pos関数

Pos関数はDelphi 2009ではRawByteString、WideString、そしてUnicodeStringの3つのオーバーロードバージョンがあります。引数はどれもSubStrとSの2つを取るわけですが、ここに大きな落とし穴があります。

UTF8Stringを使ってコードを書いてみます。

var
  U: UTF8String;
begin
  U := 'あいうえお';
  ShowMessage(IntToStr(Pos('い',U)));
end;

UTF-8なので、ShowMessageで表示されるべき値は4です。
しかし、実行すると2と表示されます。何故でしょうか?

    文字列定数の罠

先ほどの例は何の変哲もないPos関数を使った検索のコードに見えます。しかし、実はこのコードでは、'い'がUnicodeStringと判定されるため、Pos関数はUnicodeStringのオーバーロードが呼ばれてしまっています。(UTF8Stringなので、本来はRawByteStringが呼ばれるべき)このため、UTF8StringからUnicodeStringに変換されてしまい、結果的に2という表示になってしまっているのです。

    解決策:全て同じ型に統一する

この問題を解決するためには、以下のコードに修正すればOKです。

var
  U: UTF8String;
begin
  U := 'あいうえお';
  ShowMessage(IntToStr(Pos(UTF8String('い'),U)));
end;

'い'をUTF8Stringでキャストしました。これで引数の全てがUTF8Stringに統一されています。実行結果もRawByteStringが呼び出され、4と表示されます。

本稿の執筆時点では複数の引数を持つ関数・手続きのオーバーロードバージョンの呼び出し優先度については、詳細な調査をしていません。しかし、引数として与える変数の型がバラバラだというのは明らかにおかしなコーディングなので、絶対にやめるべきだと言えるでしょう。

次からのサーバー応答:: ETNASC03