wstring_convertやwbuffer_convertを使ってワイド文字列とマルチバイト文字列との変換を実現する方法を考えました。

wstring_convertやwbuffer_convertは、前回(VC++のwstring_convertやwbuffer_convertがちょっと変)書いたように、その名前に反してワイド文字との変換に使えるようになっていません。std::use_facetの戻り値を使えるように作られていないためです。

そこをどうにかできないかとずっと考えていて、やっと思いつきました。use_facetで得たcodecvtに移譲するクラスを作るという手法です。それをwstring_convertやwbuffer_convertに渡せばいいわけです。

#include <array>
#include <iostream>
#include <locale>
#include <codecvt>
#include <string>
 
template<typename CodeCvt>
class codecvt_forwarder : public CodeCvt
{
public:
  //using std::codecvt_base::result; // ← Visual C++ 2015でダメだった。
  using typename CodeCvt::result;
  using typename CodeCvt::state_type;
  using typename CodeCvt::intern_type;
  using typename CodeCvt::extern_type;
 
  explicit codecvt_forwarder(const std::locale& loc)
    : CodeCvt(), m_loc(loc), m_cvt(std::use_facet<CodeCvt>(m_loc))
  {
  }
 
  ~codecvt_forwarder() {}
 
  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 m_cvt.out(state, 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 m_cvt.unshift(state, to, to_end, to_next);
  }
  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 m_cvt.in(state, from, from_end, from_next, to, to_end, to_next);
  }
  int do_encoding() const noexcept override
  {
    return m_cvt.encoding();
  }
  bool do_always_noconv() const noexcept override
  {
    return m_cvt.always_noconv();
  }
  int do_length(
    state_type& state,
    const extern_type* from,
    const extern_type* from_end,
    std::size_t max) const override
  {
    return m_cvt.length(state, from, from_end, max);
  }
  int do_max_length() const noexcept override
  {
    return m_cvt.max_length();
  }
 
private:
  std::locale m_loc;
  const CodeCvt& m_cvt;
};
 
class wstring_convert_by_locale
  : public std::wstring_convert<
    codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>, wchar_t>
{
public:
  wstring_convert_by_locale(const std::locale& loc)
    : wstring_convert(
      new codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>(loc))
  {
  }
};
 
class wbuffer_convert_by_locale
  : public std::wbuffer_convert<
    codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>, wchar_t>
{
public:
  wbuffer_convert_by_locale(std::streambuf* buf, const std::locale& loc)
    : wbuffer_convert(
      buf,
      new codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>(loc))
  {
  }
};
 
int main()
{
  std::locale loc("");
  std::locale::global(loc); // setlocaleを呼び出すことを意図している。
 
  wstring_convert_by_locale cv(loc);
 
  wbuffer_convert_by_locale buf(std::cin.rdbuf(), loc);
  std::wistream my_wcin(&buf);
 
  std::wstring input;
  std::getline(my_wcin, input);
 
  std::array<char, 256> input_mbs;
  wcstombs(input_mbs.data(), input.c_str(), input_mbs.size());
 
  std::wstring wcs = cv.from_bytes(input_mbs.data());
  std::string mbs = cv.to_bytes(wcs);
 
  std::cout << mbs << std::endl;
}

クラステンプレートcodecvt_forwarderは、codecvtの全virtualメンバー関数をオーバーライドしています。いずれも、すべてメンバー変数m_cvtのpublicメンバー関数に処理を丸投げしています。m_cvtはuse_facetで得たオブジェクトです。

変換に使うcodecvtについては、std::codecvt_byname<char, wchar_t, std::mbstate_t>を使う方法も考えられます。一応汎用性を重視して、上記プログラムではstd::localeとstd::use_fasetの組み合わせとしました。

FreeBSDでこんな感じで動きました。

$ echo あいうえお | iconv -t EUC-JP | env LANG=ja_JP.eucJP ./a.out | iconv -f EUC-JP
あいうえお
$ echo あいうえお | iconv -t Shift_JIS | env LANG=ja_JP.SJIS ./a.out | iconv -f Shift_JIS
あいうえお
$ echo あいうえお | iconv -t UTF-8 | env LANG=ja_JP.UTF-8 ./a.out | iconv -f UTF-8

Visual C++ 2015でも同じように動きます(試したのはコードページ932でだけです)。

c++ – libc++ vs VC++: Can non-UTF conversions be done with wstring_convert? – Stack Overflowの回答の1つにある、std::codecvt(またはstd::codecvt_byname)の派生クラスを作る方法よりまともな方法が出来上がったと自負しています。


スポンサード リンク

この記事のカテゴリ

  • ⇒ wstring_convertやwbuffer_convertでwchar_tとcharとを変換する