2011-04-19

C++0xのUTF-8対応に問題あり

今まであまり気にしていなかったのだが、C++0xのUTF-8対応には、非常に深刻な問題があるように思われる。

C++0xでは、u8 encoding prefixを使うことによって、UTF-8でエンコードされた文字列リテラルが使える。

u8"あいうえお" ;

しかし、このリテラルの型は、char const [16]なのだ。(UTF-8では、ひらがなは一文字3バイトを要する。null終端を付け加えて、サイズは16となる。)

しかし、charという型は、歴史的に、あらゆるマルチバイト文字コードを格納するのに使われている。charを入力に受け取った時点で、それがどんなエンコードを使っているかは分からないのだ。

つまり、以下の様な関数を書いた場合、

// UTF-8の文字列を入力に取る関数
void f( char const * ptr )
{
// ptrがUTF-8文字列を参照している保証はない
}

ptrの指し示すNULL終端文字列は、UTF-8でエンコードされているかもしれないし、それ以外の実装依存のエンコードが使われているかもしれない。問題は、関数のユーザーに、UTF-8を強制させる方法はないということだ。ユーザーはencoding prefixのない素の文字列リテラルを渡すかもしれない。あるいは、仮にUTF-8文字列リテラルを使う意図があったとしても、u8プレフィクスを付け忘れるかもしれない。

f(u8"あいうえお") ; // 正しいリテラル、UTF-8エンコードが使われる
f(u"あいうえお") ; // コンパイルエラー、型が違う
f(U"あいうえお") ; // コンパイルエラー、型が違う
// u8を付けるのを忘れた
f("あいうえお") ; // 実行時エラーになるかもしれない、実装依存のエンコードが使われる

たとえ、ユーザーがタイプミスを犯したとしても、このエラーをコンパイル時に検出することはできない。実行時でも、入力が本当に妥当なUTF-8かどうかを検証するのは、容易ではない。第一、たまたま妥当なUTF-8の文字列として解釈できるマルチバイト文字コードのバイナリ列など、いくらでもあるだろう。単にassertを使うこともできない。デバッグは、手動で行うか、あるいはかなり高度なヒューリスティックな手法を使うものになるだろう。あまりよろしくない。

とはいえ、これまでもUTF-8の受け皿としてchar型が使われてきたことも事実だ。なかなか難しい。

もちろん、別の型があるからといって、壊れた入力がなくなるわけではない。ただ、u8を書き忘れたなどというような、初歩的なミスをコンパイル時に発見できるだけである。

どうも、C++0xのFDISに、素直に賛成できなくなってきた。後はユーザー定義リテラルか。

2 comments:

Egtra said...

ソフトウェアの動作確認を「実装依存のエンコード」というのがUTF-8という環境でしか行なっていない場合、u8を付け忘れたとしても人間が気づかない(全く同じバイト列になる)のが困ったところだと私は思います。それを非UTF-8環境で動かそうとするとたちまちボロが出ると。

惑星 said...

規格で何とかするのが一番ですが、それ以外の解決法としては、アノテーションでしょうか。

void Func(const char* __utf8);
Func("ABC");

これでコンパイル時に警告を出すとか。規格屋からみると、なんだそりゃかっこ悪すぎって感じだと思いますが(笑)。