C++でC風ライブラリを作る(UTF-32からUTF-8への変換編)
高木です。こんにちは。
ここまででいくつかの関数を作ってきました。
ここから先は、何らかの形で文字ないしは文字列が関わってきます。
処理系に依存せずに文字(列)を扱うことは容易ではないですが、今回は処理系に対していくつかの仮定を設けることで、何とか対応していきたいと考えています。
我々が現実に遭遇する可能性が高い処理系という意味では、char16_t
型はUTF-16、char32_t
型がUTF-32を表すことになると考えられます。
また、wchar_t
型はUTF-16またはUTF-32のどちらかを表すと考えてよいでしょう。
問題なのはchar
型だけかと思います。
char
型については、LinuxやMacOSXでは実質的にUTF-8を扱うようです。
マイコンの場合は事情がさまざまですが、フリースタンディング環境になると思いますのでここでは対象外とします。
ややこしいのはWindowsで、日本語環境の場合はシフトJIS(より厳密にはCP932)によって文字を表現することになります。
ファイルの操作など、アプリケーションの外部で管理している文字列を扱う場合には、そのプラットフォームにとってネイティブなエンコーディングを使う必要があります。
WindowsはUTF-16がネイティブなのですが、他のプラットフォームとの互換性を考慮して多バイト文字による表現を基本とすることにします。
さて、前置きはこれぐらいにして、そろそろ本題に入りたいと思います。
標準Cライブラリにはwctomb
関数があり、ワイド文字から多バイト文字への変換を行います。
今回は、cttomb
関数を作成し、各種文字型から多バイト文字への変換を行えるようにします。
ここでいう多バイト文字は常にUTF-8を使うものとします。
標準Cライブラリのwctomb
関数はロケールに依存していたのですが、cttomb
関数はロケールに依存しません。
ところで、標準CライブラリにはMB_LEN_MAX
およびMB_CUR_MAX
というマクロがあります。
これらのマクロは多バイト文字を構成する最大バイト数を表すもので、前者はロケールにかかわらない最大バイト数、後者は現在のロケールでの最大バイト数です。
前者が定数式に展開されることが保証されているのに対して、後者はそうとはいえないため、配列の要素数に指定するなら通常はMB_LEN_MAX
を使うことになると思います。
これらのマクロに対応する定数もmb_len_max
およびmb_cur_max
として定義することにします。
対称性のため2つ用意しますが、どちらも値は4に定義します。
ISI/IEC 10646では6バイトまであり得るのですが、これを許すといろいろ問題が起きるのと、実用上4バイトまでで支障がないので、最大バイト数は4とします。
それでは実装です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | namespace cloverfield { constexpr int mb_len_max = 4; constexpr int mb_cur_max = 4; inline int cttomb(char* s, char32_t c32) { int result = -1; if (s == nullptr) return 0; if (c32 < 0x80) { *s = static_cast<char>(c32); result = 1; } else if (c32 < 0x800) { s[0] = static_cast<char>(0xc0 | (c32 >> 6)); s[1] = static_cast<char>(0x80 | (c32 & 0x3f)); result = 2; } else if (c32 < 0x10000) { s[0] = static_cast<char>(0xe0 | (c32 >> 12)); s[1] = static_cast<char>(0x80 | ((c32 >> 6) & 0x3f)); s[2] = static_cast<char>(0x80 | (c32 & 0x3f)); result = 3; } else if (c32 < 0x200000) { s[0] = static_cast<char>(0xf0 | (c32 >> 18)); s[1] = static_cast<char>(0x80 | ((c32 >> 12) & 0x3f)); s[2] = static_cast<char>(0x80 | ((c32 >> 6) & 0x3f)); s[3] = static_cast<char>(0x80 | (c32 & 0x3f)); result = 4; } return result; } } |
凝ったことはせずに意図的にベタベタに書いています(そういう方針ですから)。
本来であれば、char16_t
型やwchar_t
型に対する多重定義も行いたいところです。
しかし、それらの型を扱う場合には内部状態が発生してしまいます。
具体的にいうと、サロゲートペアの片割れが指定された場合には、それを内部状態として保持した上で、次に呼び出された際に合成する必要があるのです。
そうした内部状態を完全に隠蔽してしまうのはあまり得策ではありません。
標準Cライブラリにもwcrtomb
関数というのがありますが、これがまさに状態を格納するオブジェクトを引数として渡せるようにしたものです。
次回は、このwcrtomb
関数に対応するctrtomb
関数を作成し、その後に改めてcttomb
関数に戻ることにしましょう。