今まであまり気にしていなかったのだが、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に、素直に賛成できなくなってきた。後はユーザー定義リテラルか。
ソフトウェアの動作確認を「実装依存のエンコード」というのがUTF-8という環境でしか行なっていない場合、u8を付け忘れたとしても人間が気づかない(全く同じバイト列になる)のが困ったところだと私は思います。それを非UTF-8環境で動かそうとするとたちまちボロが出ると。
ReplyDelete規格で何とかするのが一番ですが、それ以外の解決法としては、アノテーションでしょうか。
ReplyDeletevoid Func(const char* __utf8);
Func("ABC");
これでコンパイル時に警告を出すとか。規格屋からみると、なんだそりゃかっこ悪すぎって感じだと思いますが(笑)。