C++で wstring を使って2バイト文字と1バイト文字を統一的に扱うには,
mbstowcs(3) を使って const char * から wchar_t * に変換して, wstring に
格納するのが普通なのだと思う。
さらに iconv(3) で明示的にコード変換をする手もあるが, そこはロケールにお任せ
することにして, wstring には EUC がそのまま入っているのだと思っていた。
..ところが, wchar_t「あ」の中身を表示してみると, A4A2ではなく, 42 30という
謎のコードが。泉谷君に相談して二人で調べてみると, どうも wchar_t の中身は
いつの間にかUTF-16になっているらしい。(!)
検索してもそんな話は全然ないし, mbstowcs(3) の中にも書いていないのですが,
ロケールは入力コードを指定するために使われるのであって, 内部的には変換されて
UTF-16で持っていることがわかった。
手元の環境では sizeof(wchar_t) = 4 だが,
Unicodeのテーブル
を見ると「あ」= 0x3042 で, リトルエンディアンなので「あ」の中身は
42 30 0 0
となっている模様。
正統的には, ロケールに従って元の文字コードに戻して文字クラスを判別するのかも
しれないが, そんなの遅くてやっていられないので, 以下のようなコードを地道に
書くことに。
#include <endian.h>
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ubyte(w) (*((char *)&(w)+1))
#define lbyte(w) (*((char *)&(w)))
#else
#define ubyte(w) (*((char *)&(w)))
#define lbyte(w) (*((char *)&(w)+1))
#endif
wtype_t wtype (wchar_t w)
{
unsigned char c1 = ubyte(w), c2 = lbyte(w);
if (0x4E <= c1 && c1 <= 0x9F)
return ZEN_KANJI;
..
}
wstring の中身が(普通の方法では)UTF-16というのはあまり聞かない話
なので, (専門の人には知られた話かもしれませんが)ちょっと書いてみました。
というか iswctype(3) が荒っぽすぎるので, 自分で文字クラスの関数を書かないと
いけないというのはどうよ, みたいな気が少ししたりして。。
# ちなみに通常の自然言語処理では, 単語を整数に直して後の処理をすることが多い
ので, こういうことを心配する場合は普通はあまり多くない, と思います。
おまけ。
上のコードをデバッグする時に,
if (t == LATIN_CNTL)
return "LATIN_CNTL";
if (t == LATIN_SPACE)
return "LATIN_SPACE";
....
のようなコードを延々と書く必要があって, 何とかならないものかと思っていた。
(今回に限った話ではなく, 前にも同じように思ったことがあった。)
そういえば, cppに関連するマニアックな方法があったような..と思って
K&Rの4.11.2節を見てみると, foo(x) をcppで置換する時, #x とするとxがクオート
された文字列になって "x" となるらしい。(xは呼び出したときの文字列に置換される。)
ということで, 上のコードは
#define inspect_type(t,type) { if (t == type) return #type; }
inspect_type(t, LATIN_CNTL);
inspect_type(t, LATIN_SPACE);
...
と書いておくと, cppが置換して上と同じコードにしてくれる。(g++ -E で見ると
わかる。) ブラボー。初めて使った機能でした。
ちなみに, これは「変数の名前」を"文字列"=データに変換できるということなので,
かなり面白い。たとえば,
#define varexec(v) ((void(*)(void))#v)()
のようなマクロを定義しておくと, 変数vの名前を文字列にして, それを関数だと
思って実行することができるはず。だから例えば, 原理的には
ASCIIで hello world
のような感じで
#define varexec(v) ((void(*)(void))#v)()
int
main (int argc, char *argv[])
{
int PTXHHHH0Z_18RVX75ow = -1; /* 中身は何でもよい */
varexec(PTXHHHH0Z_18RVX75ow);
/* = ((void(*)(void))"PTXHHHH0Z_18RVX75ow")(); が実行される */
}
とできるということ(!)。
*1
通常, プログラミング言語では変数は値だけが問題で, 名前は実行には完全に無関係
というナイーブすぎるソシュール主義があるわけですが(注:
前に書いた文章
),
それがここでは完全に逆転していて, 「変数は名前だけが問題で, 中身は何でもよい」
ことになっている。イカス! :-) :-)
*1: 問題は, C言語で許される文字だけで(例えばx86の)機械語としても有効な"名前"
をつけるのが難しいかも知れない, ということだと思います。
なお, ((void(*)(void))__FILE__)(); とすると,
"ファイル名プログラミング" ができます
("PTXHHHH0Z_18RVX75ow" というファイルにこの1行を書いておく)。この場合は文字の
制限はあまりなさそうです。