文字エンコードつきの文字列
こういうのはどうだろう:
template <class Encoding, class Char = typename Encoding::char_type, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> > class basic_estring : public std::basic_string<Char, Traits, Allocator> , public Encoding { };
つまり自分の文字エンコーディングが何であるか知っている文字列クラス。
大抵のプログラムで使われる文字エンコーディングは決め打ちで良いだろうから、こんな感じのものを用意しておけば:
template <typename Char = char16_t> class encoding_utf16 : public static_encoding { }; template <typename Char = char> class encoding_utf8 : public static_encoding { }; template <typename Char = char> class encoding_cp932 : public static_encoding { }; template <typename Char = char> class encoding_euc_jp : public static_encoding { };
basic_stringに比べてオブジェクトのサイズが大きくなったりはしない。
エンコーディングに関する情報は静的に取り出せる:
template <class Encoding> class encoding_traits; template <class Encoding, class Char, class Traits, class Allocator> inline char const* encoding_name(const basic_estring<Encoding, Char, Traits, Allocator>& /* es */) { return encoding_traits<Encoding>::name(); } template <> class encoding_traits<encoding_utf8> { public: inline static char const* name() { return "UTF-8"; } };
もちろん、ひとつの文字列クラスで複数の文字コードを扱いたい場合もあるだろう。その場合は:
enum encoding_scheme_t { BINARY = 0, ASCII = 1, UTF8 = 2, UTF16 = 3, UTF32 = 4, ..., CP932, EUCJP, ..., EBCDIC, ... }; template <typename Char> class encoding_any : public dynamic_encoding { public: encoding_scheme_t encoding; encoding_any(encoding_scheme_t ec) : encoding(ec) {} };
でもって basic_estring はこれを食えるようにする:
basic_estring::basic_string(char const* ptr, encoding_scheme_t ec = Encoding::default_encoding()) : basic_string<Char, Traits, Alloc>(ptr) , Encoding(ec) { }
他のコンストラクタも同様にencoding_shcemeを取れるよう追加する。
この場合、エンコーディングに関する情報はインスタンスがないと取れない:
template <class Encoding, class Char, class Traits, class Allocator> inline char const* encoding_name(const basic_estring<Encoding, Char, Traits, Allocator>& es) { return encoding_traits<Encoding>::name(es.encoding); }
これだとbasic_estring::encodingの有無を判断する必要があるので、enable_ifで分ける:
template <class Encoding, class Char, class Traits, class Allocator> inline char const* encoding_name(const basic_estring<Encoding, Char, Traits, Allocator>& /* es */, std::enable_if<std::is_base_of<Encoding, static_encoding>::type*=0) { return encoding_traits<Encoding>::name(); } template <class Encoding, class Char, class Traits, class Allocator> inline char const* encoding_name(const basic_estring<Encoding, Char, Traits, Allocator>& es, std::enable_if<std::is_base_of<Encoding, dynamic_encoding>::type*=0) { return encoding_traits<Encoding>::name(es.encoding); }
そもそも動的に動くことしか考えていない関数なんかでは、いちいちenable_ifもないだろう。その場合に備えてエンコーディングの番号を返す関数があると便利だろう:
template <class Encoding, class Char, class Traits, class Allocator> inline encoding_scheme_t encoding_scheme(const basic_estring<Encoding, Char, Traits, Allocator>& /* es */, std::enable_if<std::is_base_of<Encoding, static_encoding>::type*=0) { return encoding_traits<Encoding>::scheme(); } template <class Encoding, class Char, class Traits, class Allocator> inline encoding_scheme_t encoding_scheme(const basic_estring<Encoding, Char, Traits, Allocator>& es, std::enable_if<std::is_base_of<Encoding, dynamic_encoding>::type*=0) { return encoding_traits<Encoding>::scheme(es.encoding); }
引数ありのschemeは引数をそのまま返すだけだが、引数なしのschemeはEncodingに静的に割り当てられた値を返す。ということは
template <typename Char = char> class encoding_utf8 : public static_encoding { static const encoding_scheme_t scheme = UTF8; };
のようになるんだろう。
動的な方は、静的なエンコード型の集合として書けるような気がする。
もちろんこのエンコーディング情報は文字コード変換や文字のイテレーションなんかに使うわけだ。
template <class Encoding, class Char, class Traits, class Allocator> std::wstring to_wstring(const basic_estring<Encoding, Char, Traits, Allocator>& es) { return to_wstring_helper<Encoding, Char, Traits, Allocator>::convert(es); } template <class Encoding, class Char, class Traits, class Allocator> class to_wstring_helper; template <class Encoding, class Traits, class Allocator> class to_wstring_helper<Encoding, char, Traits, Allocator> { inline static std::wstring convert(const basic_estring<Encoding, char, Traits, Allocator>& es) { #ifdef WIN32 int len = ::MultiByteToWideChar(win32_encoding_codepage(es), win32_encoding_flags(es), ...); wchar_t* buf = ...; ... int ret = ::MultiByteToWideChar(win32_encoding_codepage(es), win32_encoding_flags(es), ...); if (ret == 0) { throw ...; } return buf; } #elsif HAS_ICONV ... } }; template <class Traits, class Allocator> class to_wstring_helper<native_wide_encoding, wchar_t, Traits, Allocator> { inline static std::wstring convert(const basic_estring<native_wide_encoding, wchar_t, Traits, Allocator>& es) { return es; } };
native_wide_encodingは、コンパイル時にコンパイル単位のワイド文字列リテラルを表現するのに使用されているとコンパイラが仮定するエンコーディング、あるいは、コンパイル単位のワイド文字列リテラルの内部表現に用いられたエンコーディングのこと、とする。実態としてはたぶん、Windows上だとUTF-16で、Linux等ではUTF-32になるのかな。FreeBSD等のCSIなケースでもワイド文字列リテラル自体には何らかのエンコーディングを決めておく必要があるだろうから、この定義で大丈夫じゃないだろうか。あとコンパイル単位を跨ったネイティブエンコーディングが定義できる場合は、これと別にglobal_native_wide_encodingとか定義しておくのかな。なくても困らない気はするけど。
あと「文字集合とエンコーディングは別概念だが、その辺はどうするか?」という点については、encoding_traitsの方で吸収しちゃえばいいんじゃない?と思う。たとえば、
template <> class encoding_traits<encoding_utf8> { static const char* charset_names() { return "Unicode"; } }; template <> class encoding_traits<encoding_utf16> { static const char* charset_names() { return "Unicode"; } }; template <> class encoding_traits<encoding_euc-jp> { static const char* charset_names() { return "JIS X 0208-1990;JIS X 0212-1990"; } };
とか。
で、この方法なら、std::basic_stringにも取り込めるんじゃないかなあ、と思うけどどーでしょう。従来のstd::basic_stringは
template <class Char, class Traits, class Alloc> using std::basic_string = basic_estring<encoding_binary, Char, Traits, Alloc>;
とかにしておいて、encoding_binaryは「エンコード不明」の意味のstatic_encodingとしておけば、既存のコードは破壊しないでしょうし。