char8_tによせて

C++標準化委員会、ついに文字とは何かを理解する: char8_tという記事が話題だってので、つらつらと書いてみました。

「グリフ」について

グリフ(glyph)という言葉の定義をめぐって でも触れられていますが、「グリフ」という言葉が「字体」を指すのか「字形」を指すのかってのは議論がありますね。文字コードの文脈では普通「字形」の意味だとして話を進めることが多いように思います。

CJK統合漢字について

Wikipediaの記事にまとまっていますが、実際に推進していたのは中国みたいですね。うまくやればあんまり問題なかったんでしょうが、あんまりうまく行かなかったんですが、それでも国ごとにその国の過去にあった文字コードとの互換性は取れているので、実際の所CJK統合漢字ってあんまり問題にはなってないと思うんですよね。中国語フォントと日本語フォントを切り替えないといけないって問題はありますけど、それは仕方なくない?だってさぁ、アルファベットは元から統合されてるんだし。

まぁ、トルコ語のアルファベットは分離しておいた方がよかったと思います。Wikipediaのトルコ語の項目にちらっと示唆されていますが、大文字と小文字の対応が違うんですよね…。

MicrosoftUnicodeといえば、もれなくUTF-16を指す。」について

元々はUnicodeは16bit固定長で、それを使ってWindows NTを実装していたら、いつのまにかUnicodeは16bitには納まらなくなっていて、仕方ないのでUTF-16に拡張したっていう悲しい経緯であって、UTF-16そのものの、あるいはワイド文字のいけてなさとは関係ない気がしました。

ワイド文字について

ワイド文字の歴史

ワイド文字については何度か書いたことがあります。

そもそもワイド文字という概念はUnicode以前からあって、元々はDEC漢字のような日本語UNIX環境の開発から生まれ、日本語UNIX環境がAT&Tの本家UNIXに取り込まれることで世界に紹介され、C89にwchar_tが取り込まれ、C++にも採用されるといった順序になっています。

この本家UNIXの国際化の世界観というのがEUCで、複数の文字集合をG0/G1/G2/G4にマップし、それを内部表現32bit、外部表現は1-4バイトで表現していました。

その他のLinuxとかとかではUTF-32だ、という事が多い。これはコードポイントと一致するエンコードだからだろうか。

これは前述の時代の名残でwchar_tが32bitで定義されていることが多かったため、自然とそこにUTF-32を詰めるようになっただけだと思います。元々そこに詰められていたのはEUCの類だったわけです。

という経緯が分かると、なぜC/C++標準で執拗にUnicode決めうち仕様を避けているのかが分かってくるのではないでしょうか。wchar_tにUnicode以外の何かを詰めたコードは世の中に多数存在するのです。

ワイド文字はなぜいけてないか

さて、なぜワイド文字はいけてないかというと、サロゲートペアは決定的な理由ではないでしょう。16bit Unicode固定長という夢の世界が否定されても、Windows NTやmacOSは既に作られてしまっているわけで、それらとデータをやりとりするにはそれを元にしたUTF-16サロゲートペアでがんばるというのは自然な発想でしょう。またBMP外の文字や絵文字などを処理する際のつらさはUTF-8でもUTF-16でも同じくらい辛いわけで、UTF-16を批判する理由にはなりません。決定的な理由が出てこない限りは、互換性と、UTF-16にあるASCIIとその他の文字を平等に扱うという三方一バイト損的な美しい大義を捨てる理由にはなりません。

では、その決定的な理由とは何か。Web世界とやりとりすることが増えたからでしょう。Webの世界はHTTPの上で成り立っており、HTTPはASCIIを基本として全てが組み上げられています。そんなASCIIの世界をUTF-16で扱うには毎回変換を行う必要がありますし、万が一不正なバイトが入っていた場合のエラーハンドリングを行うにはバイト列のまま扱う処理を用意しなければいけません。そんな事を行うならば最初からバイト列でがんばるか…UTF-8を使った方がよいでしょう。

この結果が90%のWebサイトがUTF-8という現実なのであり、これと同じことがついにC++標準にも及んできたと言うことなのでしょう。

なお、この辺の事情はUnixでのファイルシステムでも同様で、パスがバイト列で表されていて/とヌル文字以外は何でも入るのでだいたいはUTF-8なんだろうけれど例外的な状況も扱えないといけないという事情がありますね。(そもそもUTF-8ファイルシステム用に生まれたんだし)

文字列について

https://togetter.com/li/1301253 で提示されている、「文字列」は文字の列としては扱いづらいという話はその通りだと思うのです。そういう中身が不透明な文字列型は例えば(C++よくわかんないんでCで書きますけど)こんな感じ?

struct {
  rsize_t size;
  int8_t encoding; /* UTF-8, UTF-16, or UTF-32 */
  int8_t valid_p;  /* true, false, or unknown */
  union {
    char8_t *u8;
    char16_t *u16;
    char32_t *u32;
  } chars;
}

前述の通り不正なバイト列もコンテナとしては入れられるけど、文字列用関数に渡すとエラーになるみたいな感じになるのだと思います。でも結局文字列を扱う関数は気合で誰かが書かないといけないし、C++ってそういう関数を各言語という面も少なからずあるので、まずchar8_tから入れたんでしょうかね。

そしてchar8_tについて

と、ここまで考えると一周回って char8_t って本当に必要だったの?という疑問がわいてきます。提案であるchar8_t: A type for UTF-8 characters and strings (Revision 5)を読むに、charにはその環境ごとのdefault localeの文字、つまりWindowsでいえばANSIをいれて、それとは別にUTF-8を入れる型を用意したいって話かな。unsigned charuint8_tでいい気もしますけどね。

ところで、バイト列とUTF-8文字列を区別したいという観点だと、バイト列をunsigned charで扱い、UTF-8文字列をchar8_tで扱うとした場合、文字列は暗黙的にバイト列として扱えるけど、その逆は明示的に指定しないとキャストできないって言う使い心地になるので、それはそれでHTTP絡みのコードを書くときは便利になるかもしれませんね。