C++でC風ライブラリを作る(UTF-16からUTF-8への変換編)
高木です。こんばんは。
前回はUTF-32からUTF-8への変換ということでcttomb
関数を作りました。
しかし、この関数を他の文字型、例えばchar16_t
型などで多重定義しようとすると、ひとつの値だけで文字が完結しないケースがあります。
異体字セレクタなんかは別としても、サロゲートペアぐらいは何とか処理したいものです。
そこで、標準Cライブラリのwcrtomb
関数に対応するctrtomb
関数を作ってはどうかと考えました。
ところが、wcrtomb
関数が変換処理を継続できるのは、ISO-2022-JPのようなシフトシーケンスを処理できるということではないかと思います。
その意味ではちょっと違うのかもしれませんが、可能な限り頑張ってみたいと思います。
ctrtomb
関数の仕様を決める前に、
wcrtomb
関数についておさらいすることにしましょう。
wcrtomb
関数の関数原型は次のようになっています。
1 | size_t wcrtomb(char *s, wchar_t wc, mbstate_t *ps); |
この関数は結構複雑で、それぞれの引数に空ポインタやナル文字を指定することができ、さらにその組み合わせによっても振る舞いが変わります。
順序は前後しますが、もっとも簡単なところから見ていきます。
3番目の引数ps
に空ポインタを指定した場合は内部的な匿名のオブジェクトが指定されたものとして扱われます。
1番目の引数s
が空ポインタではなく、2番目の引数wc
がナル文字(L'\0'
)の場合には、s
が指す配列に状態*ps
を初期状態に戻すためのシフトシーケンスが格納され、次いでナル文字が格納されます。
s
が空ポインタの場合はwc
は無視されます。
そして、内部的な匿名の配列buf
を用いて次のような呼び出しを行ったときと同じように振る舞います。
1 | wcrtomb(buf, L'\0', ps); |
もっとも典型的な使い方は、s
が空ポインタではなく、wc
もナル文字ではない場合です。
この場合は、ワイド文字wc
を現在のロケールに基づいて多バイト文字に変換した結果をs
が指す配列に書き込みます。
さらに、シフト状態を*ps
に書き込みます。
wcrtomb
関数の返却値はs
に書き込まれたバイト数です。
s
が空ポインタの場合も、内部的な匿名の配列に書き込まれるバイト数が返されます。
エラーが発生した場合は(size_t)-1
が返されます。
これを踏まえて、ctrtomb
関数の仕様を決めていくことにします。
その前に、標準Cライブラリのmbstate_t
型はオブジェクト型であることしか規定されていないため、これを流用するのは困難です。
ここは独自のmbstate_t
を定義することにしましょう。
1 2 3 4 | namespace cloverfield { using mbstate_t = std::uint32_t; } |
std::uint32_t
型に定義しておけば、char32_t
型の値もそっくりそのまま保持できますので、とりあえずこうしておきましょう。
さて、いよいよctrtomb
関数です。
まずは関数原型を決めます。
1 2 3 4 | namespace cloverfield { std::size_t ctrtomb(char* s, char16_t uc, mbstate_t* ps); } |
3番目の引数ps
が空ポインタの場合は、内部の匿名オブジェクトが使われるところはwcrtomb
関数と同じでよいでしょう。
1番目の引数s
が空ポインタの場合の振る舞いも、内部の匿名の配列buf
を用いて、
1 | ctrtomb(buf, u'\0', ps); |
と同じで問題ないはずです。
問題なのは、*ps
に何を格納すべきかです。
今回は、uc
の値をそのまま
*ps
に格納することにします。
こうすることで、uc
がサロゲートペアの前半だった場合には、次に呼び出されたときに適切なサロゲートペアになるかどうかを判定できます。
サロゲートペアが成立しない場合や値が不正な場合には例外を送出することでエラーを検出できます。
uc
がナル文字の場合、*ps
にサロゲートペアの前半部が格納されていれば、強引に後半部を出力してもいいのですが、潔く例外を送出してしまったほうがいいでしょう。
とくに問題がないなら、s
が指す配列にはナル文字を格納することにします。
これで実装可能なところまで仕様は固まったと思います。
ちょっと長くなりすぎましたので、実装については次回に回すことにします。