パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ソースコード(自作のcodecvtファセット)

ファイルストリームバッファに用いる文字コード変換ファセット(codecvtファセット)クラスを自作する。

iconvライブラリを利用し(iconvが扱う)任意の文字コード間の変換を一般に扱うクラステンプレートとして、以下の二つに対応する。

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

iconvはC++標準ではないがPOSIX標準で互換性は高い。本サイトプログラミング環境ではMSYS2でコンパイラと同時にiconvライブラリも導入していてそのまま利用可能となっている。インクルードファイル、ライブラリファイル共に標準ディレクトリにあり、インクルードファイル(iconv.h)とライブラリファイル(libiconv.aまたはlibiconv.dll.a)の指定だけで良い。作成したクラスはクラステンプレートあるいは名前空間stdクラステンプレートの部分特殊化なので、ヘッダオンリーライブラリ(インクルードファイルのみ)として利用できる。

覚え書き
ただしiconv.hはコメントにASCII外のUTF-8符号を含みコンパイラ入力文字コードをデフォルト(-finput-charset=UTF-8)としないとインクルードに失敗する。従って8ビット文字列にシフトJISを用いるアプリケーションでソースコードにそのリテラルを書き込む場合、iconvを利用するには入力文字コードをUTF-8、実行文字コードをシフトJIS(-fexec-charset=CP932)とする。すなわちシフトJIS文字列リテラルを文字コードUTF-8で書き込むという不自然な事態を招く。あるいはASCII外文字を削除したiconv.hを作り標準より前に探索されるインクルードディレクトリに置けば良いが、メンテナンスを考えれば好ましくない。

本項目の内容はcodecvtを考察するで詳細な議論を追加する。提案した自作codecvtには使用上の制限と不具合があり、そちらで確認いただきたい。さらに本項目の最後に提示する疑問点について、本サイトの解答も示している。

ソースコード

TMyCodeCvt.h

#ifndef TMYCODECVT_H_INCLUDED
#define TMYCODECVT_H_INCLUDED
#include <locale>
#include <iconv.h>
#include <memory>
template<typename T,typename U> class TMyCodeCvtStateIconv
{
public:
using result=std::codecvt_base::result;
using intern_type=typename T::type;
using extern_type=typename U::type;
private:
std::shared_ptr<std::remove_pointer<iconv_t>::type> in_;
std::shared_ptr<std::remove_pointer<iconv_t>::type> out_;
public:
TMyCodeCvtStateIconv()
:in_{iconv_open(T::encoding(),U::encoding()),iconv_close}
,out_{iconv_open(U::encoding(),T::encoding()),iconv_close}
{}
private:
template<typename from_type,typename to_type> result do_io
(const iconv_t& cd
,const from_type* from,const from_type* from_end,const from_type*& from_next
,to_type* to,to_type* to_end,to_type*& to_next) const
{
if (from>from_end||to>to_end) {return std::codecvt_base::error;}
auto* fromBuf=reinterpret_cast<char*>(const_cast<from_type*>(from));
auto fromBytes=(from_end-from)*sizeof(from_type);
auto* toBuf=reinterpret_cast<char*>(to);
auto toBytes=(to_end-to)*sizeof(to_type);
auto iconvRet=iconv(cd,&fromBuf,&fromBytes,&toBuf,&toBytes);
from_next=reinterpret_cast<from_type*>(fromBuf);
to_next=reinterpret_cast<to_type*>(toBuf);
if (iconvRet==(size_t)(-1))
{
return (errno==E2BIG||errno==EINVAL)?
std::codecvt_base::partial:std::codecvt_base::error;
}
return std::codecvt_base::ok;
}
public:
result do_in
(const extern_type* from,const extern_type* from_end,const extern_type*& from_next
,intern_type* to,intern_type* to_end,intern_type*& to_next) const
{return do_io(in_.get(),from,from_end,from_next,to,to_end,to_next);}
result do_out
(const intern_type* from,const intern_type* from_end,const intern_type*& from_next
,extern_type* to,extern_type* to_end,extern_type*& to_next) const
{return do_io(out_.get(),from,from_end,from_next,to,to_end,to_next);}
result do_unshift(extern_type* to,extern_type* to_end,extern_type*& to_next) const
{
const auto* from_next=static_cast<intern_type*>(0);
return do_out(0,0,from_next,to,to_end,to_next);
}
int do_length(const extern_type* from,const extern_type* from_end,size_t max) const
{
const auto* from_next=from;
intern_type to[max];
auto* to_next=to;
do_in(from,from_end,from_next,to,to+max,to_next);
return from_next-from;
}
static bool do_always_noconv() noexcept {return false;}
static int do_max_length() noexcept {return MB_LEN_MAX;}
static int do_encoding() noexcept {return 0;}
};
namespace std
{
template <template<typename,typename> class CodeCvtStateTemplate,typename T,typename U>
class codecvt
<typename CodeCvtStateTemplate<T,U>::intern_type
,typename CodeCvtStateTemplate<T,U>::extern_type
,CodeCvtStateTemplate<T,U>>
:public __codecvt_abstract_base
<typename CodeCvtStateTemplate<T,U>::intern_type
,typename CodeCvtStateTemplate<T,U>::extern_type
,CodeCvtStateTemplate<T,U>>
{
public:
using result=codecvt_base::result;
using intern_type=typename CodeCvtStateTemplate<T,U>::intern_type;
using extern_type=typename CodeCvtStateTemplate<T,U>::extern_type;
using state_type=CodeCvtStateTemplate<T,U>;
public:
static locale::id id;
public:
explicit codecvt(size_t refs=0)
:__codecvt_abstract_base<intern_type,extern_type,state_type>{refs}
{}
protected:
~codecvt() override {}
result do_in
(state_type& state,const extern_type* from,const extern_type* from_end,const extern_type*& from_next
,intern_type* to,intern_type* to_end,intern_type*& to_next) const override
{return state.do_in(from,from_end,from_next,to,to_end,to_next);}
result do_out
(state_type& state,const intern_type* from,const intern_type* from_end,const intern_type*& from_next
,extern_type* to,extern_type* to_end,extern_type*& to_next) const override
{return state.do_out(from,from_end,from_next,to,to_end,to_next);}
result do_unshift
(state_type& state,extern_type* to,extern_type* to_end,extern_type*& to_next) const override
{return state.do_unshift(to,to_end,to_next);}
int do_length
(state_type& state,const extern_type* from,const extern_type* from_end,size_t max) const override
{return state.do_length(from,from_end,max);}
bool do_always_noconv() const noexcept override {return state_type::do_always_noconv();}
int do_max_length() const noexcept override {return state_type::do_max_length();}
int do_encoding() const noexcept override {return state_type::do_encoding();}
};
template<template<typename,typename> class CodeCvtStateTemplate,typename T,typename U>
locale::id codecvt
<typename CodeCvtStateTemplate<T,U>::intern_type
,typename CodeCvtStateTemplate<T,U>::extern_type
,CodeCvtStateTemplate<T,U>>::id;
}
template<typename myStateT,typename stateT=mbstate_t> class TMyCodeCvt
:public std::codecvt<typename myStateT::intern_type,typename myStateT::extern_type,stateT>
{
public:
using result=std::codecvt_base::result;
using intern_type=typename myStateT::intern_type;
using extern_type=typename myStateT::extern_type;
using state_type=stateT;
private:
myStateT myState_;
public:
explicit TMyCodeCvt(size_t refs=0)
:std::codecvt<intern_type,extern_type,state_type>{refs},myState_{}
{}
protected:
~TMyCodeCvt() override {}
result do_in(state_type& state
,const extern_type* from,const extern_type* from_end,const extern_type*& from_next
,intern_type* to,intern_type* to_end,intern_type*& to_next) const override
{return myState_.do_in(from,from_end,from_next,to,to_end,to_next);}
result do_out(state_type& state
,const intern_type* from,const intern_type* from_end,const intern_type*& from_next
,extern_type* to,extern_type* to_end,extern_type*& to_next) const override
{return myState_.do_out(from,from_end,from_next,to,to_end,to_next);}
result do_unshift(state_type& state
,extern_type* to,extern_type* to_end,extern_type*& to_next) const override
{return myState_.do_unshift(to,to_end,to_next);}
int do_length(state_type& state
,const extern_type* from,const extern_type* from_end,size_t max) const override
{return myState_.do_length(from,from_end,max);}
bool do_always_noconv() const noexcept override {return myState_.do_always_noconv();}
int do_max_length() const noexcept override {return myState_.do_max_length();}
int do_encoding() const noexcept override {return myState_.do_encoding();}
};
namespace NMyEncoding
{
#define ENCODING_TRAITS(NAME,ENCODING,CHAR_T) \
struct NAME {using type=CHAR_T;static const char* encoding() {return ENCODING;}};
ENCODING_TRAITS(UTF16,"UTF-16LE",wchar_t);
ENCODING_TRAITS(UTF8,"UTF-8",char);
ENCODING_TRAITS(ShiftJIS,"CP932",char);
ENCODING_TRAITS(JIS,"ISO-2022-JP",char);
}
#endif // TMYCODECVT_H_INCLUDED

説明

以下により構成される。

  • 文字コード変換を実装するTMyCodeCvtStateIconvクラステンプレート
  • 標準ライブラリ供給codecvtクラステンプレートのTMyCodeCvtStateIconvによる部分特殊化
  • ライブラリ標準codecvtクラステンプレート派生のTMyCodeCvtクラステンプレート
  • TMyCodeCvtStateIconvに文字コードを指定するクラスが定義されたNMyEncoding名前空間

TMyCodeCvtStateIconvクラステンプレート

ライブラリ利用スキーム1と2で共用するクラステンプレートで、codecvtクラステンプレートの各仮想メンバ関数(do_in、do_outなど)を実装する。パブリックメンバ関数はそれぞれcodecvt仮想メンバ関数からコールされる事を想定し、名称が対応を明示する。テンプレート仮引数Tが内部文字コードを指定し、Uが外部文字コードを指定する。文字コードを指定するクラスは例えば以下である。

struct ShiftJIS
{
using type=char; // element type
static const char* encoding() {return "CP932";} // iconv encoding name
};

iconvライブラリはiconv_t型の変換記述子で変換を管理する。TMyCodeCvtStateIconvは二つの変換記述子を持ち入出力それぞれに割り当てる。iconv_t型はvoid*にtypedefされていてデリータ定義のstd::shared_ptrスマートポインタで保持する。TMyCodeCvtStateIconvはライブラリ利用スキーム1でcodecvtクラステンプレート仮引数stateTの実引数として使用する。これをファイルストリームバッファクラステンプレート(basic_filebuf)が利用するには、TMyCodeCvtStateIconvを後述のtraits::state_typeに指定する。traits::state_typeは以下を要件とする(N4659 24.2.2/p4)。変換記述子をshared_ptrに保持させる理由は、コピー代入/構築で変換記述子を二重に閉じないための処置である。

  • コピー代入可能(CopyAssignable)
  • コピー構築可能(CopyConstructible)
  • デフォルト構築可能(DefaultConstructible)

codecvtクラステンプレートのTMyCodeCvtStateIconvによる部分特殊化

ライブラリ利用スキーム1を実装する。codecvtクラステンプレートの特殊化として、std名前空間内のcodecvtクラステンプレートの第三仮引数stateTをTMyCodeCvtStateIconvクラステンプレートとする部分特殊化を行う(20.5.4.2.1/p1)。なお将来拡張に備えクラステンプレートもテンプレートテンプレート仮引数で受ける。stateTは相互に変換される文字コードペアを選択する(N4659 25.4.1.4/p2)。stateTはその参照が各仮想メンバ関数の第一仮引数stateであり、stateは"変換状態"の記憶に用いることができる(Nicolai M. Josuttis, The C++ Standard Library, Boston, Addison-Wesley, 1999; Boston, Addison-Wesley, 2004, p.721、Angelika Langer et al., Standard C++ IOStreams and Locales,Reading, Addison-Wesley, 2000, p.282)が規格は何も定義しない(25.4.1.4/p3、25.4.1.4.2/p4)。basic_filebufクラステンプレートは文字コード変換に以下のa_codecvtを用いる(30.9.2/p5)。traitsはクラステンプレート第二仮引数で、所有されるストリームクラステンプレートの仮引数を受け継ぐ(30.9.3.1/p2、30.9.4.1/p2、30.9.5.1/p2)。

const codecvt<charT,char,typename traits::state_type>& a_codecvt=use_facet<codecvt<charT,char,typename traits::state_type>>(getloc());

use_facet関数テンプレートはパブリックスタティックメンバ変数idでlocaleの所有するファセットを検索する。codecvt部分特殊化の特殊化はそれぞれ独自のid値を持つ(25.3.1.1.2/p1、25.3.1.1.3/p1)。TMyCodeCvtStateIconv特殊化のインスタンスはクライアントが所有し、パブリックメンバ関数を通し仮想メンバ関数第一仮引数stateの実引数として参照され、文字コード変換はstateが行う。

codecvt部分特殊化はmingw-w64標準ライブラリ実装の__codecvt_abstract_baseクラステンプレートを継承することでパブリックメンバ関数の定義を省略している。__codecvt_abstract_baseが得られない場合は全て自ら定義しなければならない。

TMyCodeCvtクラステンプレート

ライブラリ利用スキーム2を実装する。TMyCodeCvtクラステンプレートは任意のcodecvt特殊化を置き換えるが、主な目的はstateTをmbstate_tとしたライブラリ標準codecvt特殊化(N4659 25.4.1.4/p3)の置き換えにある。TMyCodeCvtは置き換えられるcodecvt特殊化を継承し、仮想メンバ関数をオーバーライドするがid値は維持する(25.3.1.1.2/p1、25.3.1.1.3/p1)。テンプレート仮引数myStateTはTMyCodeCvtStateIconvクラステンプレートの特殊化を想定し、そのインスタンスをTMyCodeCvtはメンバ変数myState_として持ち、文字コード変換はmyState_が行う。仮想メンバ関数第一仮引数stateは全く用いられない。なお、codecvtファセットにおけるメンバ変数使用の問題点について後に考察を加える。

NMyEncoding名前空間

TMyCodeCvtStateIconvクラステンプレート仮引数に文字コードを指定する実引数として与えるクラスを定義する名前空間。名前空間を使用する必然性は無いが、グローバル名前空間の不必要な汚染を避けるため導入した。ENCODING_TRAITSはクラス定義を支援するマクロで形式的にNMyEncoding名前空間内に記述したが、名前空間とは無関係の存在である事は言うまでもない。

動作確認

iconvの扱える文字コードの内のUTF-16LE、UTF-8、CP932の非常に単純なケースでのみ動作確認している。以下ではワイド実行文字コードはデフォルト(UTF-16)のままでコンパイルされていて、実行パスにUTF-8で記述された"input_utf8.txt"があるものとして、内部文字コードUTF-16と外部文字コードUTF-8の変換を確認する。"input_utf8.txt"にはホワイトスペースで区切られた数字の文字列が書かれているものとして、整数型変数との入出力を行う。以下においてTMyStateはTMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::UTF8>のエイリアスとする。

codecvtクラステンプレートの特殊化(ライブラリ利用スキーム1)

codecvtファセットにcodecvt<wchar_t,char,TMyState>を用いる。これはcodecvtクラステンプレートのTMyCodeCvtStateIconvクラステンプレートによる部分特殊化の、TMyStateによる暗黙的特殊化である。

#include <iostream>
#include <fstream>
#include "TMyCodeCvt.h"
using TMyState=TMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::UTF8>;
struct TMyTraits:std::char_traits<wchar_t>
{
using pos_type=std::fpos<TMyState>;
using state_type=TMyState;
};
int main()
{
auto fin=std::basic_ifstream<wchar_t,TMyTraits>("input_utf8.txt");
auto fout=std::basic_ofstream<wchar_t,TMyTraits>("output_utf8.txt");
auto loc=std::locale{std::locale{std::locale{std::locale{}
,new std::codecvt<wchar_t,char,TMyState>{}}
,new std::num_get<wchar_t,std::istreambuf_iterator<wchar_t,TMyTraits>>{}}
,new std::num_put<wchar_t,std::ostreambuf_iterator<wchar_t,TMyTraits>>{}};
// For completeness you must add all specialized facets which satisfy the
// followings, such as num_get and num_put above.
// - A facet belongs to N4659 25.3.1.1.1/p4 Table 70 with template parameter
// InputIterator or OutputIterator.
// - The template parameter has a default argument istreambuf_iterator<wchar_t,mbstate_t>
// or ostreambuf_iterator<wchar_t,mbstate_t> respectively.
// - The facet with the default template argument eventually is a part of
// 25.3.1.1.1/p3 Table 69.
fin.imbue(loc);
fout.imbue(loc);
for (;;)
{
auto strVal=std::basic_string<wchar_t,TMyTraits>{};
auto intVal=int{};
fin>>strVal>>intVal;
if (!fin) {break;}
fout<<strVal<<'\t'<<intVal<<std::endl;
}
return 0;
}

TMyTraitsクラスをbasic_filebufクラステンプレートの第二仮引数traitsに指定する。TMyTraitsはstate_typeの他にpos_type(N4659 24.2.2/p2)をfpos<TMyState>と定義しないとstatic_assertエラーでコンパイルできない。fposクラステンプレートのデフォルト(プライマリテンプレート)はstreamoff型メンバ変数への処理が主でテンプレート実引数へ依存せず(include\c++\13.2.0\bits\postypes.h:70)、つまりプライマリテンプレートによる暗黙的特殊化で良い。

basic_filebuf<wchar_t,TMyTraits>を使用するストリーム(通常はbasic_ifstream<wchar_t,TMyTraits>、basic_ofstream<wchar_t,TMyTraits>、basic_fstream<wchar_t,TMyTraits>)はこのcodecvtファセットで内部文字コードUTF-16、外部文字コードUTF-8で文字および文字列の入出力を行う。ただし文字列型はwstringではなくbasic_string<wchar_t,TMyTraits>となる。これだけでは文字および文字列以外のデータ(例えば数値)入出力に失敗するが、その理由はlocaleのデフォルト所有するファセット(25.3.1.1.1/p3 Table69)では不足なためである。例えばデフォルト所有のnum_put<wchar_t>は実際はnum_put<wchar_t,ostream_iterator<wchar_t,mbstate_t>>なので(25.4)、basic_ofstream<wchar_t,TMyTraits>がテンプレート実引数に従いnum_put<wchar_t,ostream_iterator<wchar_t,TMyTraits>>を利用しようとしても得られない。サンプルコードはnum_putとnum_getのみ対応し通常の数値データ入出力を可能としたが、その他にmoney_put、money_get、time_put、time_getが必要である。

ライブラリ標準codecvtファセットの置き換え(ライブラリ利用スキーム2)

codecvtファセットとしてTMyCodeCvt<TMyState>を用い、ライブラリ標準codecvt<wchar_t,char,mbstate_t>に置き換える。これはTMyCodeCvtクラステンプレートのTMyStateによる暗黙的特殊化である。

#include <iostream>
#include <fstream>
#include "TMyCodeCvt.h"
using TMyState=TMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::UTF8>;
int main()
{
auto fin=std::wifstream("input_utf8.txt");
auto fout=std::wofstream("output_utf8.txt");
auto loc=std::locale{std::locale{},new TMyCodeCvt<TMyState>{}};
fin.imbue(loc);
fout.imbue(loc);
for (;;)
{
auto strVal=std::wstring{};
auto intVal=int{};
fin>>strVal>>intVal;
if (!fin) {break;}
fout<<strVal<<'\t'<<intVal<<std::endl;
}
return 0;
}

疑問点

サイト作成者は以下の疑問を持つ。一応の解答を別項目で示したが、より正確な解答をご存知であれば教示いただきたい。

覚え書き
本サイトの文字コード変換に関する用語を定義しておく。"部分変換"はcodecvtのinメンバ関数あるいはoutメンバ関数がpartialを返す事で、すなわち変換元バッファ全データが変換成功して変換先バッファに出力されている状態にないことである。"部分変換"の特殊な状況が"部分符号シーケンス"で、変換元バッファ末尾の符号配列が完全でなく、文字コード変換に追加の符号を必要とする状況である。前者は"partial conversion"、後者は"partial coding sequence"に対応する。

部分変換の実装

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

仮想メンバ関数do_in、do_outは(from_end-from)個以下の変換元(source)符号を変換し(to_end-to)個以下の変換先(destination)符号を変換先へ収納する(N4659 25.4.1.4.2/p2)。[from,from_end)全てが変換できなかった(from_next<from_end)場合は値partialを返す(25.4.1.4.2/p5)。from_next==from_endでpartialを返す場合は変換された符号列が変換先符号列に全て収納されていない、あるいは変換先符号の生成にはさらなる変換元符号列の追加が必要(部分符号シーケンス)である事を示す(25.4.1.4.2/p5)。変換できない文字に遭遇した場合はfrom_nextとto_nextを変換成功した符号の次を指したまま(25.4.1.4.2/p2)値errorを返す(25.4.1.4.2/p5)。

部分符号シーケンスとなる場合の実装は二つのスキームが考えられる。

  • from_nextをfrom_endへ移動し、部分符号シーケンスを記憶(例えば2符号長文字の第一符号をstateに代入)し、partialを返す(部分変換スキーム1)。
  • from_nextを部分符号シーケンスの先頭へ移動し、partialを返す(部分変換スキーム2)。

ライブラリ利用スキーム1と2が共用するTMyCodeCvtStateIconvクラステンプレートは部分変換スキーム2によるが、教科書は部分変換スキーム1(Nicolai M. Josuttis, The C++ Standard Library, Boston, Addison-Wesley, 1999; Boston, Addison-Wesley, 2004, p.722)と部分変換スキーム2(Angelika Langer et al., Standard C++ IOStreams and Locales, Reading, Addison-Wesley, 2000, p.284)で対立する。部分変換スキーム2では、変換先がバッファで符号列長が1文字の符号長よりも短い(例えば2符号長文字なのに1符号長しかない)場合に無限ループに陥ると予想される(1符号長でも収納さえできればバッファとして機能できるのに)。

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

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

テンプレート仮引数stateTの参照が各仮想メンバ関数(do_in、do_outなど)の第一仮引数stateであり、stateは変換状態記憶に用いることができるが規格は何も定義しない(N4659 25.4.1.4/p3、25.4.1.4.2/p4)。変換状態記憶は以下の二つのスキームが考えられる。

  • クライアント所有stateT型変数への参照である仮想メンバ関数第一仮引数(state)で記憶する(状態記憶スキーム1)。
  • codecvtファセットクラスに任意型メンバ変数(例えばmyState_)を定義し、そこに記憶する(状態記憶スキーム2)。

ライブラリ利用スキーム1は状態記憶スキーム1であり、ライブラリ利用スキーム2は状態記憶スキーム2である。ライブラリ利用スキーム2はライブラリ標準codecvtファセット(25.4.1.4/p3)を置換するためstateTはmbstate_tに固定される。これを状態記憶スキーム1で実装するにはTMyCodeCvtStateIconvクラステンプレート特殊化(前述サンプルコードではTMyState)のインスタンスをmbstate_t型stateに押し込むことになるが、そもそもmbstate_tが実装依存で一般解が存在しない。mingw-w64標準ライブラリ実装に限ればmbstate_tはint型で、かつて類似のケースで状態記憶クラスのポインタをキャストしてstateに保持させるという危険な実装を見かけた記憶があるが、64ビットのポインタ型はint型サイズの2倍でこれも不可能となった。もう少し頑張れるなら、状態記憶クラスの配列を用意しstateにそのインデックスを保持させても良いかもしれない。

複数のファイルストリームバッファがcodecvtファセットを共有する場合を考える。状態記憶スキーム1はファイルストリームバッファ毎に変換状態を記憶し問題ないが、状態記憶スキーム2はクラスメンバ変数(例えばmyState_)に記憶を共有する。部分変換スキーム2ならmyState_は部分符号シーケンスの記憶が不要で一見安全だが、JIS(ISO-2022-JP)などステートフルな文字コードはシフト状態も記憶して共有はエラーを招く。にもかかわらず前述サンプルコードはfinとfoutでTMyCodeCvt<TMyState>を共有するが、入出力に異なった変換記述子を割り当てたまたま安全なだけであり、状態記憶スキーム2は一般には共有できない。

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

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

ファイルストリームバッファ(basic_filebuf)は内部型から外部型へのマッピングに1:N(1個の内部型符号をN個の外部型符号にマッピングする)を仮定する(N4659 25.4.1.4.2/p3および脚注236)ため、答えは明らかに否であろう。加えて外部型のサイズは8ビットに固定される(30.9.2/p5)。これでは使用できる文字コード変換は大きく制約され、特にウィンドウズアプリケーションでは内部型は事実上UTF-16なので完全に規格合致したファイルストリームバッファ入出力は望めない。なぜならUTF-16からUTF-8(あるいはシフトJIS、あるいは...)への文字コード変換はN:Mとならざるを得ないためである。

この制約はcodecvtファセット自体の制約でなく、basic_filebufの使用できるcodecvtファセットの制約である。本サイトはしかし制約にとらわれる事なく以下のcodecvtファセットをbasic_filebufで使用する。

  • codecvt_utf8_utf16<wchar_t>
  • codecvt<wchar_t,char,TMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::UTF8>>
  • TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::UTF8>>
  • TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF16,NMyEncoding::ShiftJIS>>
  • TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF8,NMyEncoding::ShiftJIS>>

これらは全て文字コード変換N:Mであるが、少なくとも本サイトのサンプルコードでは問題となっていない。