パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
codecvtを考察する

C++標準ライブラリのcodecvtファセットについてサイト作成者の理解を整理する。

codecvtはlocaleインスタンスが保持するファセットの一つである。localeインスタンスは様々なファセットを一括して任意のストリーム(basic_stream)に設定(imbue)する。ストリームはlocaleインスタンスを通して各ファセットを利用する。codecvtを利用するストリームはbasic_fstream(ifstream、wifstream、ofstream、wofstream)、より正確にはストリームの所有するbasic_filebuf(filebuf、wfilebuf)に限定され、これはストリームを限定しない他のファセットと大きく異なる。codecvtは文字コードを変換する。主たる目的はファイル入出力での利用だが、標準唯一の文字コード変換フレームワークとしてその他の目的にも利用できる。

本項目はロケールとファセット、とりわけcodecvtファセットの文字コード変換ライブラリ利用による自作を議論する。アプリケーションでのそれぞれの利用方法は日本語アプリケーションをどのように開発するかなどを参照いただきたい。

ロケール

ロケールは言語や地域を定義するパラメータセットで、インターフェースはこれを用いてユーザー最適な方法で情報交換する。

mingw-w64標準ライブラリの実装を確認する。

ロケールとファセット

C++標準ライブラリはロケール(JTC1/SC22/WG21 N4659 25)をlocaleクラス(25.3)で扱い、そのインスタンスはパラメータセットを複数のファセットで構成する(C++ロケール)。ファセットはC言語(JTC1/SC22/WG14 N1570 7.11)/POSIX(IEEE 1003.1-2017 7. Locale)のカテゴリを概ね踏襲し、いずれかのカテゴリ(N4659 25.4)に属するクラステンプレートで表す。全てのクラステンプレートは文字型を第一のテンプレート仮引数に受ける。いくつかのクラステンプレートはサフィックス_bynameの付く派生クラステンプレート(例えばctype_byname)を持ち、ロケール名(後述のstd_name)をコンストラクタの仮引数に受ける(25.3.1.1.2/p5、25.4.1.2、25.4.1.5、25.4.3.2、25.4.4.2、25.4.5.4、25.4.6.4、25.4.7.2)。

カテゴリ C言語/POSIX 説明 ファセット/クラステンプレート
ctype LC_CTYPE 文字分類と大小文字変換 ctype(25.4.1.1), codecvt(25.4.1.4)
numeric LC_NUMERIC 通貨以外の数値形式 numpunct(25.4.3.1), num_get(25.4.2.1), num_put(25.4.2.2)
collate LC_COLLATE 照合順序 collate(25.4.4.1)
time LC_TIME 日付時間形式 time_get(25.4.5.1), time_put(25.4.5.3)
monetary LC_MONETARY 通貨型式 moneypunct(25.4.6.3), money_get(25.4.6.1), money_put(25.4.6.2)
messages LC_MESSAGES 情報/診断メッセージ形式 messages(25.4.7.1)

localeのコンストラクタを整理する(25.3.1.2)。categoryはカテゴリを示す整数型である(25.3.1.1.1)。

コンストラクタ 構築するコピー
locale() グローバルC++ロケール
locale(const locale& other) other
locale(const char* std_name) std_nameを名前とするC++ロケール
locale(const std::string& std_name)
locale(const locale& other, const char* std_name, category cat) otherにstd_nameロケールのcatカテゴリ導入
locale(const locale& other, const std::string& std_name, category cat)
template<class Facet> locale(const locale& other, Facet* f) otherにfファセット導入
locale(const locale& other, const locale& one, category cat) otherにoneのcatカテゴリ導入

グローバルC++ロケールはlocale::globalスタティックメンバ関数が設定するが、単なるlocaleインスタンスに過ぎずグローバルな挙動に直接影響する物ではないため、"グローバル"は誤解を招く呼称だと思う。なおC言語標準ライブラリとの関係には注意が必要で後述する。

localeインスタンスは以下の標準ファセットまたはその派生を必ず所有する(N4659 25.3.1.1.1/p3)。localeコンストラクタのcatカテゴリが導入するファセットは、最低でもこれらの中でそのカテゴリに該当するものを含む(25.3.1.1.1/p2)。なおC++20はchar8_tの追加でcodecvt<char16_t,char,mbstate_t>とcodecvt<char32_t,char,mbstate_t>は非推奨となり、codecvt<char16_t,char8_t,mbstate_t>とcodecvt<char32_t,char8_t,mbstate_t>に代替された(N4861 28.3.1.1.1/p2)。

カテゴリ ファセット
collate collate<char>, collate<wchar_t>
ctype ctype<char>, ctype<wchar_t>, codecvt<char, char, mbstate_t>, codecvt<char16_t, char, mbstate_t>, codecvt<char32_t, char, mbstate_t>, codecvt<wchar_t, char, mbstate_t>
monetary moneypunct<char>, moneypunct<wchar_t>, moneypunct<char, true>, moneypunct<wchar_t, true>, money_get<char>, money_get<wchar_t>, money_put<char>, money_put<wchar_t>
numeric numpunct<char>, numpunct<wchar_t>, num_get<char>, num_get<wchar_t>, num_put<char>, num_put<wchar_t>
time time_get<char>, time_get<wchar_t>, time_put<char>, time_put<wchar_t>
messages messages<char>, messages<wchar_t>

std_nameを与えて構築したlocaleインスタンスは_bynameの付くバージョンにstd_nameを渡して構築した派生ファセットを所有する。mingw-w64を含むほとんどの実装でstd_nameが"C"は"C"ロケール(最小環境)、空文字列""はネイティブ環境、"POSIX"は"C"と同義である(N4659 25.3.1.2/p4、N1570 7.11.1.1/p3)。ネイティブ環境はPOSIX準拠としてLANG環境変数に従う(IEEE 1003.1-2017 8.2 Internationalization Variables)が、mingw-w64ではC言語標準ライブラリとの関係で問題を生じる。グローバルC++ロケールのデフォルトは"C"ロケールである。mingw-w64実装はstd_nameに"C"と"POSIX"のみを許容し、LANG環境変数が未定義か"C"あるいは"POSIX"の場合のみ""を許容する。これ以外は実行時に例外を送出する。_bynameの付くファセットを"C"または"POSIX"以外で直接構築しても同様である。この制限はmingw-w64がウィンドウズネイティブである事が理由ではなく、msys2サブシステムやCygwinのGCCも同じ制限を持ち、GCCのウィンドウズ実装共通の問題らしい。

異なるテンプレート実引数による特殊化(例えばnum_get<int>)はcatカテゴリに含まない。

int main()
{
auto loc1=std::locale{{},new std::num_get<int>};
auto loc2=std::locale{{},loc1,std::locale::numeric};
std::cout<<std::boolalpha;
std::cout<<"Loc1 has std::num_get<int>:"<<std::has_facet<std::num_get<int>>(loc1)<<std::endl;
std::cout<<"Loc2 has std::num_get<int>:"<<std::has_facet<std::num_get<int>>(loc2)<<std::endl;
}

結論として、mingw-w64は"C"以外のC++ロケールを名前で構築できず、"C"ロケールへのfファセット導入が唯一の変更手段となる。この制限はそれほど致命的でなく、第一に日本語コンソールアプリケーションでも"C"ロケールによる入出力が一般的で、第二にデスクトップアプリケーションならそもそもそういった入出力が不要で、第三に必要ならwxWidgetsなどのライブラリが同等の機能を供給する。こういった理由で本サイトはロケール変更に強い動機を持たないが、codecvtファセットを例外として文字コード変換に重用する。

C言語標準ライブラリのロケール

参考として、C言語標準ライブラリのロケール(C言語ロケール、"C"ロケールと混同しないように)を確認する。C言語ロケールはsetlocale関数により名前でグローバルに設定する(N1570 7.11.1.1)。strftime関数などのロケール依存関数はこの設定で挙動が変わる。

mingw-w64を含むGCCウィンドウズ実装は既述のように"C"以外のC++ロケールを名前で構築できない一方で、setlocaleはC言語ロケールを変更できる。setlocaleはグローバルC++ロケールを変更しないが、locale::globalは名前付きC++ロケールを与えた場合にグローバルC++ロケールとC言語ロケールを変更して(N4659 25.3.1.5/p2)、両ロケールを一括する唯一の方法とする(Nicolai M. Josuttis, The C++ Standard Library, Boston, Addison-Wesley, 1999; Boston, Addison-Wesley, 2004, pp.697-698)。しかしmingw-w64はそのようなC++ロケールを与える事が不可能であり、結局locale::globalはC言語ロケールを変更できない。

覚え書き
なおmingw-w64(正確にはGCC)実装の標準ワイド文字列codecvtファセットの8ビット符号文字コードはC言語ロケールに従う。locale::globalは規格上C言語ロケールを変更できるため"グローバルC++ロケールは単なるlocaleインスタンスに過ぎずその変更はグローバルな挙動に影響しない"は実装次第で誤った言明になる。一方でmingw-w64(正確にはGCCウィンドウズ実装)でlocale::globalがC言語ロケールを結局変更できず、結論として標準ワイド文字列codecvtファセットの8ビット符号文字コードの設定はC言語標準ライブラリのsetlocaleを用いなければならない。

mingw-w64(ウィンドウズネイティブ)とmsys2サブシステムGCC(POSIX依存)で確認する。setlocaleをコールして日付時間をstrftimeで書式化して出力する。出力はコマンドプロンプト(シフトJIS)とPOSIX互換ターミナル(UTF-8)で確認した。

#include <stdio.h>
#include <locale.h>
#include <time.h>
void test(const char* std_name,const tm* currtm)
{
char buf[80]={};
const auto* loc=setlocale(LC_ALL,std_name);
if (loc) {strftime(buf,sizeof(buf),"%c",currtm);}
printf("%-30s%-30s%s\n",std_name,loc,buf);
}
int main()
{
auto currtime=time_t{};
time(&currtime);
const auto* currtm=localtime(&currtime);
test("en_US",currtm);
test("de_DE",currtm);
test("ja_JP.sjis",currtm); // Shift-JIS
test("ja_JP.eucjp",currtm); // EUC-JP
test("ja_JP.utf8",currtm); // UTF-8
test("english_usa",currtm);
test("german_germany",currtm);
test("japanese_japan.932",currtm); // Shift-JIS
test("japanese_japan.20932",currtm); // EUC-JP
test("japanese_japan.65001",currtm); // UTF-8
}
コンパイラ ロケール名 setlocaleの戻り値 コマンドプロンプト(シフトJIS) POSIX互換ターミナル(UTF-8)
mingw-w64 en_US (null)
de_DE (null)
ja_JP.sjis (null)
ja_JP.eucjp (null)
ja_JP.utf8 (null)
english_usa English_United States.1252 1/27/2022 9:00:00 AM 1/27/2022 9:00:00 AM
german_germany German_Germany.1252 27.01.2022 09:00:00 27.01.2022 09:00:00
japanese_japan.932 Japanese_Japan.932 2022/01/27 9:00:00 2022/01/27 9:00:00
japanese_japan.20932 Japanese_Japan.20932 2022/01/27 9:00:00 2022/01/27 9:00:00
japanese_japan.65001 (null)
msys2サブシステムGCC en_US en_US Thu, Jan 27, 2022 9:00:00 AM Thu, Jan 27, 2022 9:00:00 AM
de_DE de_DE Do, 27. Jan 2022 09:00:00 Do, 27. Jan 2022 09:00:00
ja_JP.sjis ja_JP.sjis 2022年01月27日 09時00分00秒 2022▒N01▒▒27▒▒ 09▒▒00▒▒00▒b
ja_JP.eucjp ja_JP.eucjp 2022年01月27日 09時00分00秒 2022ǯ01▒▒27▒▒ 09▒▒00ʬ00▒▒
ja_JP.utf8 ja_JP.utf8 2022年01月27日 09時00分00秒 2022年01月27日 09時00分00秒
english_usa (null)
german_germany (null)
japanese_japan.932 (null)
japanese_japan.20932 (null)
japanese_japan.65001 (null)

mingw-w64とmsys2サブシステムGCCは使用できるロケール名が異なり、前者はマイクロソフトに準拠し後者はPOSIXに準拠する。前者はしかしマイクロソフトのドキュメントする全てが使えるわけではなく(例えば"ja-JP"が使えない)、理由はmingw-w64のランタイムがmsvcrt.dll(MSVCRT)である一方、ドキュメントは新しいユニバーサルCランタイム(UCRT)で書き換えられているためと思われる。同じ理由でmingw-w64のsetlocaleはドキュメントに反しUTF-8を扱えない。mingw-w64でロケール名に与える文字コードはコードページ(整数)で指定するが利用できるものは限られる。日本語を表現できるのは932(シフトJIS)と20932(EUC-JP)だけで、20290(EBCDIC日本語)はsetlocaleできるが文字コード変換でエラー(例外送出)となり、その他はsetlocaleに失敗する。不明の理由でsetlocaleが機能しなくなる場合があり、例えば20290を設定すると以降のsetlocaleは全て失敗する。

サンプルコードでmingw-w64は日本語文字を出力せず文字コード不一致による文字化けは確認できない。msys2サブシステムGCCはPOSIX互換ターミナルで文字コード不一致による文字化けが確認できるが、コマンドプロンプトでは何故か文字化けしない。

覚え書き
コンパイラの導入はmsys2サブシステムGCCは導入していない。導入はPOSIX互換ターミナルから行う。統合開発環境のカスタマイズ(1)を参考にすればCode::Blocksでの利用もそれほど困難ではない。
user@THINKPAD-L430 MSYS ~
$ pacman -S --needed gcc
覚え書き
msys2サブシステムGCCのコマンドプロンプト出力が文字コード不一致でも文字化けしない理由を調査している。例えば以下コードを-fexec-charset=eucjpでコンパイルして日本語コマンドプロンプト(文字コードCP932)へ出力しても文字化けしない。
#include <stdio.h>
#include <locale.h>
int main()
{
setlocale(LC_ALL,"ja_JP.eucjp");
printf("日本語");
}

msys2サブシステム(mingw64/usr)はCygwinサブセットと見なせるためCygwinで調査した。古い情報を引用するが、setlocaleはコマンドプロンプトの文字コードも変更する。
Right now, if you switch the charset via the setlocale function, you also switch the charset used for console output. That's quite a unique advantage of the Cygwin console actually, because it means you always get correct output even if you switch charset on the fly.
ただしこれを正式に記述するドキュメントは見つからない。

mingw-w64のC++ロケールとC言語ロケール

C++ロケールについて、mingw-w64実装もPOSIX依存実装も共通コードを用いてPOSIXに準拠する。C言語ロケールについて、mingw-w64はmsvcrt.dllを利用してマイクロソフトに準拠し、POSIX依存はGNUランタイムglibcを利用してPOSIXに準拠する。つまりmingw-w64はC++ロケールとC言語ロケールの関係に矛盾がある。最も顕著な例はロケール名に空文字列""を与えて取得するネイティブ環境の定義で、マイクロソフト準拠はシステムロケールでスタートメニュー[設定|時刻と言語|地域|日付、時刻、地域の追加設定|地域|管理|システムロケールの変更]に従い、POSIX準拠はLANG環境変数に従う。mingw-w64はこれを解決できず、GCCウィンドウズ実装一般の問題として名前付きlocale構築を制限しているものと想像する。

ロケール名に空文字列""を与える場合の挙動をまとめる。

実装 名前付きlocale構築(C++) setlocale(C言語)
mingw-w64 LANGが未定義、"C"、"POSIX" の場合は"C"ロケール
それ以外はエラー
システムロケール(例えばjapanese_japan.932)
POSIX依存 LANG(例えばja_JP.UTF-8)

codecvtファセットの自作

本サイトはC++標準ライブラリのロケール変更に強い動機を持たないが、codecvtファセットは例外として文字コード変換に重用する。ロケールの主たる機能は文字列レベルのデータ操作である一方で、codecvtのみ文字コードという異なるレベルのデータ操作を行う。参考文献をサイト作成者訳で引用する(Corentin Jabot, Locales, Encodings and Unicode, 2020, JTC1/SC22/WG21 P2020R0, pp.1-2)。

ロケールと文字コードは別の概念だが歴史的な理由でPOSIX以降のC言語/C++は両者を統合したモデルを採用してきた。そのためC言語/C++のロケール変更は文字コードに影響し、一方で文字コードはインターフェースに直接表れない。もちろんこれはその時点で標準化されていた文字コードが全世界全ての文字を表現できず、ターゲットとする言語や地域で使うごく一部の文字のみを表現していたためである。しかしながら地域固有の文字など存在せず、例えば日本語をターゲットとする文章で「"locale"の韓国語は로케일である」と記述するのに何の問題があろうか。C++の文脈においてロケールとはターゲットとする地域、言語あるいは文化において、情報を文字化して表示するための規則と理解するべきである。

レガシーな文字コードで記述した古いファイルはハードディスクの肥やしに残り、開発者が更新を放棄した有用なアプリケーションはユニコードを扱えず、ユニコードにしても複数のフォーマットが存在する。文字コード変換の需要は今でも確実に存在するが、本サイトはcodecvtをそういった文字コード変換のフレームワークとして活用する。C++標準ライブラリが供給するcodecvtの扱う文字コードは限定される上に実装依存が大きいため、任意の文字コードを扱えるcodecvtを文字コード変換ライブラリを利用して自作してしまう。

  • バッファメモリ上での文字コード変換なので高効率が期待できる
  • codecvtの自作は外見ほどには難しくない
  • iconv、ICUなどの文字コード変換ライブラリはcodecvtから利用容易となるインターフェースを持つ

ソースコード(自作のcodecvtファセット)でiconvライブラリによるcodecvtを実装したが、本項目は実装方法をさらに詳解してICUライブラリによる実装とウィンドウズAPIによる実装を追加する。ウィンドウズアプリケーションは主にユニコード(手法)を用いるため内部文字コードはUTF-16として、議論のcodecvtは内部文字コードUTF-16、外部文字コード任意(文字コード変換ライブラリの扱えるもの)に固定する。この変換はN:Mでファイルストリームバッファの規格外(N4659 25.4.1.4.2/p3および脚注236)となるため、mingw-w64実装における安全性についても検討する。

codecvt実装方法の詳解

本サイトはライブラリ利用方法に二つのスキームを提案する。

  • codecvtクラステンプレートの特殊化(ライブラリ利用スキーム1)
  • ライブラリ標準codecvtファセットの置き換え(ライブラリ利用スキーム2)

ソースコードはiconvライブラリ、ICUライブラリ、ウィンドウズAPIによる実装を比較し、二つのスキームのメリットデメリットを検討する。詳細はソースコードに譲り結論のみ記述する。

これらの実装は双方向ファイルストリームとしてはならず、ストリーム途中でロケールを変更してはならない。多くの場合に問題とはならないが一部に不可能な条件が存在し、安全を見越してできない物と考えた方が良い。ウィンドウズAPI実装はステートフルな文字コードを扱えない。速度についてウィンドウズAPI実装は変換関数の機能制限で大きく劣り、iconvライブラリ実装とICUライブラリ実装の優劣はつけ難い。

ライブラリ利用スキーム1のメリットは、codecvtインスタンスを複数のファイルストリームで安全に共有できる。デメリットの第一は、codecvtインスタンス構築がデフォルトに限定され、コンストラクタへ引数を渡す手段が無く使用する文字コードはコンパイル時に決定されて実行時の動的設定ができない。デメリットの第二は、ファイルストリーム(basic_fstream)を含む様々なクラステンプレートの特殊化が必要になる。これは暗黙的特殊化で十分で単に実体化すれば良い。文字列(basic_string)さえも特殊化が必要で、ファイルストリームの特殊化からwstringへシフト演算子による入出力ができず、文字列の特殊化を必要とする。文字列の特殊化とwstringの相互変換はc_strメンバ関数を必要とする。シフト演算子による書式化にはいくつかのファセットの特殊化をlocaleインスタンスに追加しなければならない。

ライブラリ利用スキーム2のメリットの第一は、codecvtインスタンス構築に引数を渡す事ができる(例示したソースコードはそのように書かれていないが)ので、使用する文字コードを動的設定できる。メリットの第二は、ファイルストリーム利用でシフト演算子による入出力が普通にできる。デメリットは大きく、codecvtインスタンスを複数のファイルストリームで共有できずimbueしたlocaleインスタンスをコピーしてはならない。

N:M変換の安全性

規格はファイルストリームバッファには必ず1:N変換のcovecvtファセットを使うとする(N4659 25.4.1.4.2/p3および脚注236)。しかしウィンドウズユニコード(手法)アプリケーションは内部文字コードUTF-16で、基本多言語面外の文字は2符号長を必要としてN:M変換とならざるを得ない。mingw-w64のbasic_filebufソースコードを調査して、N:M変換codecvtファセット利用の安全性を確認する。詳細はソースコードに譲り結論のみ記述する。

ファイル読み込みは外部文字コードを可変長変換とすれば問題なく行える。UTF-32などの固定長でも可変長として扱う必要がある。ファイル書き込みは後述する部分変換スキーム2の場合に内部バッファ終端が部分符号シーケンス(例えば最後がUTF-16の2符号長文字第一符号)となるとエラーとなる。これを解消するにはbasic_filebuf::overflowメンバ関数を修正しなければならない。basic_filebuf::sputbackcメンバ関数あるいはbasic_filebuf::sungetcメンバ関数で読み込んだ符号を入力ストリームに戻す場合、内部バッファの前に戻す事はできない。これはN:M変換の問題ではなく可変長変換として扱うためであり、すなわち1:N変換でも可変長(例えばUCS-2/UTF-8変換)なら同じ問題を抱える。

結論として、N:M変換codecvtファセットのファイル読み込みは安全。ファイル書き込みは部分変換スキーム1なら安全で、部分変換スキーム2はbasic_filebuf::overflowの修正が必要。符号を入力ストリームに戻す操作はしない方が良い。

疑問点への解答

ソースコード(自作のcodecvtファセット)で三つの疑問点を提示した。本項目の考察をまとめて、以下に解答する。

部分変換の実装

部分符号シーケンスとなる場合、シーケンスを記憶せず変換元ポインタ(from_next)をシーケンス先頭に移動してpartialを返す事は、規格に合致しているか?

規格に合致するだけでなく、そのようにするべきである。つまり、部分変換スキーム1より部分変換スキーム2を採る方が良い。ただしN:M変換の場合は内部バッファ終端が部分符号シーケンスとなる場合に問題を抱える。例えばデフォルトならフラッシュせず4096番目にUTF-16の2符号長文字第一符号を出力するとエラーとなる。これを解消するにはbasic_filebuf::overflowを修正する必要がある。

教科書は部分変換スキーム1(Josuttis)と部分変換スキーム2(Langer et al.)で対立するが、ウェブ情報のほとんどが部分変換スキーム1を前提とする。JosuttisがKindle化した一方でLanger et al.が絶版なのがその理由かもしれない。部分変換スキーム1は部分符号シーケンスをstateに記憶する一般解が存在しないが、部分変換スキーム2はその必要が無い。部分変換スキーム2はバッファサイズが符号長より小さいと無限ループに陥るが、そのようなバッファはそもそも実用的でない。これは想像だが元々はステートフル前提で、部分変換スキーム2としてstateはエスケープシーケンスの選択情報のみを記憶する(従ってintで十分)としていたのではないだろうか。なおスキーム2の実装でも利用するライブラリが自ら部分符号シーケンスを記憶すればスキーム1として機能し、ICUはそういったライブラリである。

codecvtファセットクラスメンバ変数への状態記憶

変換状態記憶を各仮想メンバ関数の第一仮引数stateへの実引数でなく、クラスメンバ変数で行う事は、規格に合致しているか?

規格に合致しないと思われ、少なくとも規格は想定していない。しかしファイルストリームからの利用でコンストラクタに引数を渡して文字コードを実行時に動的設定するにはこれを用いざるを得ない。その場合、このcodecvtファセットを所有するlocaleインスタンスをコピーしてはならない。

これはC++規格ロケールオブジェクト(localeクラス)の今となっては設計ミスとしか思えない。百歩譲っても設計に文字コードの動的設定が考慮されていないと言うべきだろう。

内部型外部型1:Nの制約

内部型と外部型の関係が1:Nとならない文字コード変換は、規格に合致しているか?

ファイルストリームからの利用は規格に合致しないが、ウィンドウズユニコード(手法)アプリケーションはこれを用いざるを得ない。既述した部分変換スキーム2を採る場合の問題に加えて、このcodecvtファセットを所有するファイルストリームから読み込んだ文字(より正確には符号)をバッファに戻す操作を行ってはならない。

バッファに戻す操作については1:Nであっても可変長文字コードの場合に同じ制限を持ち、この制限は規格に合致しない事が理由ではない。いずれにせよウィンドウズの内部文字コードがUTF-16である限り、我慢しなければならない事項の一つであり続ける。UTF-32とchar32_tの美しい世界は恐らく到来しない。