Visual C++ 2015および2017 RCでは、<codecvt>の各クラステンプレートにchar16_tやchar32_tを組み合わせるとリンクエラーになるという問題があります。今回、これに対する条件付きのWorkaroundについて書きます。

まずは、エラーになるコードの例を提示します。

#include <codecvt>
#include <string>
#include <cassert>
#include <locale>
 
int main()
{
  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter_utf32;
  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter_utf16;
 
  std::string u8str = u8"\U0001F359";
 
  std::u32string u32str = converter_utf32.from_bytes(u8str);
  std::u16string u16str = converter_utf16.from_bytes(u8str);
}

これをVisual C++ 2015でコンパイル・リンクするとこんなエラーメッセージが出てきます。

u.obj : error LNK2019: 未解決の外部シンボル "public: static class std::locale::id std::codecvt::id" (?id@?$codecvt@_SDU_Mbstatet@@@std@@2V0locale@2@A) が関数 "public: __thiscall std::locale::locale >(class std::locale const &,class std::codecvt_utf8_utf16 const *)" (??$?0V?$codecvt_utf8_utf16@_S$0BAPPPP@$0A@@std@@@locale@std@@QAE@ABV01@PBV?$codecvt_utf8_utf16@_S$0BAPPPP@$0A@@1@@Z) で参照されました。
u.obj : error LNK2019: 未解決の外部シンボル "public: static class std::locale::id std::codecvt::id" (?id@?$codecvt@_UDU_Mbstatet@@@std@@2V0locale@2@A) が関数 "public: __thiscall std::locale::locale >(class std::locale const &,class std::codecvt_utf8 const *)" (??$?0V?$codecvt_utf8@_U$0BAPPPP@$0A@@std@@@locale@std@@QAE@ABV01@PBV?$codecvt_utf8@_U$0BAPPPP@$0A@@1@@Z) で参照されました。
u.exe : fatal error LNK1120: 2 件の未解決の外部参照

さて、エラーメッセージを読むと、1件目はstd::locale::id型のstaticメンバー変数std::codecvt<char16_t,char,struct _Mbstatet>::idが見つからないという内容です。2件目も同じ内容でchar32_tになっているだけです。ということは、これを自分で定義したら良いのではないでしょうか?

というわけで、こんなコードを書いたらうまくいきました。コンパイル・リンクできてちゃんと実行できました。

#include <codecvt>
#include <string>
#include <cassert>
#include <locale>
 
// この2行を追加
std::locale::id std::codecvt<char32_t, char, std::mbstate_t>::id;
std::locale::id std::codecvt<char16_t, char, std::mbstate_t>::id;
 
int main()
{
  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter_utf32;
  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter_utf16;
 
  std::string u8str = u8"\U0001F359";
 
  std::u32string u32str = converter_utf32.from_bytes(u8str);
  std::u16string u16str = converter_utf16.from_bytes(u8str);
}

ただし、この方法を使うには条件があります。それは、VC++ランタイムを静的リンクする場合(/MT/MTdコンパイルオプション)だけ使えるというものです。/MD/MDdだと、問題となるstaticメンバー変数idが__declspec(dllimport)付きの宣言となってしまうため、この方法が使えません。別のリンクエラーになります。

ところで、以下のcpprefjpの各ページのサンプルコードもこの問題に該当します。それぞれのサンプルコードの作成にあたり、私はこの方法で乗り切りました。

まとめです。

  • codecvt_utf16, codecvt_utf8, codecvt_utf8_utf16のいずれかに、char16_tかchar32_tを使うとリンクエラーが出る。
  • そうなったら、codecvtクラステンプレートののstaticメンバー変数idを自分で定義すれば、リンクが通る。

バグ報告は出ているそう(Visual C++における文字コード変換 – C++と色々)なので、将来的にはVisual C++が直ることを期待します。あと/MD, /MDdでのWorkaround欲しいです。

この記事のカテゴリ

  • ⇒ VC++ 2015のcodecvtでリンクエラーになる問題の回避策

Visual C++ 2015と2017 RCで、以下のプログラムが期待どおりに動きませんでした。

#include <iostream>
#include <sstream>
#include <iomanip>
#include <ctime>
 
int main()
{
  std::istringstream s("20170110");
 
  std::tm x = {};
  s >> std::get_time(&x, "%Y%m%d");
 
  char buf[255];
  strftime(buf, sizeof(buf), "%Y-%m-%d", &x);
  std::cout << buf << std::endl;
}

%Yで2017だけパースされることを期待しているのですが、Visual C++の実装では、20170110まで読み込んでしまうようになっていました。

libstdc++やlibc++では、ちゃんと2017だけ読み込む実装だったので、Visual C++のほうに問題がありそうです。

2017年1月15日追記: 同じ問題が報告されているのを見つけました。std::get_time fails when using formats without separators – Developer Community

この記事のカテゴリ

  • ⇒ VC++でstd::get_timeがなんかダメ
  • ⇒ VC++でstd::get_timeがなんかダメ

主にLinuxで使われるGNU C Library (glibc)では、wchar_tをUCS-4 (UTF-32)固定としている、そう私は認識しています。しかし、ずっとその根拠を確かめずにそう思い込んでいる状態だったので、気になって調べました。

マニュアルにそのような記載が無いかと探した結果、The GNU C Library: Selecting the Conversionに以下の記述を見つけました。

The wide character set is always UCS-4 in the GNU C Library.

このほか、LinuxのMan page of UNICODEには、以下の記述があります。

GNU/Linux では、C 言語の型 wchar_t は符号付き 32 ビット整数型である。 その値は C ライブラリにより (すべてのロケールにおいて) 常に UCS コードの値として解釈される。 これを GNU C ライブラリがアプリケーションに知らせるための規約として、 定数 __STDC_ISO_10646__ を定義する。 これは ISO C99 規格で指定されている。

なお、__STDC_ISO_10646__を定義している箇所も見つけました。Move __STDC_* predefined macros from features.h to stdc-predef.h. · git-mirror/glibc@ff3b3d8 · GitHubです。

というわけで、glibcにおいてwchar_tは常にUCS-4である、という認識で良さそうです。

この記事のカテゴリ

  • ⇒ glibcのwchar_tがUCS-4であることを確認した
  • ⇒ glibcのwchar_tがUCS-4であることを確認した

次の表を見てください。<time.h>あるいは<ctime>でtime_tとtm構造体との変換を行う関数をまとめました。

time_t→tm構造体 tm構造体→time_t
現地時刻 localtime mktime
UTC gmtime

表の「?」の部分、UTCでtm構造体からtime_tに変換する関数、それがCおよびC++の標準ライブラリにはありません。

仕方ないので、非標準でもいいから何かないかと探しました。どうやら、Unix系の一部にはtimegm、Visual C++には_mkgmtimeという関数があることが分かりました。

Linux glibc, BSD系列, Cygwinなど
timegm関数 (Linux, FreeBSD)
Visual C++
_mkgmtime

このほか、Linuxのmanには、Unix系一般に通用しそうな方法が説明されています。setenvで環境変数TZを設定のうえ、mktimeを呼び出すというやり方です。

なおこれ、std::chrono::system_clock::time_point ←→ std::time_t ←→ std::tm ←→ (std::put_time, std::get_time)というstd::chrono::system_clock::time_pointからの入出力の過程で必要になったので、調べました。

この記事のカテゴリ

  • ⇒ tmからtime_tにUTCで変換する関数を探した
  • ⇒ tmからtime_tにUTCで変換する関数を探した

この記事は、初心者C++er Advent Calendar 2016の23日目の記事です。


WindowsのVisual C++でこんなコードを書き始めたんです。

#include <cstdint>
#include <windows.h>
 
void f(std::uint8_t) {}
void f(std::uint16_t) {}
void f(std::uint32_t) {}
void f(std::uint64_t) {}

符号無し整数型をすべて網羅するよう、関数fを多重定義したわけです。さらにコードを書き進めます。

int main()
{
	DWORD x = 12345;
	f(x);
}

こんなコードを書いたら、コンパイルエラーになってしまいました。

t.cpp
t.cpp(12): error C2668: ‘f’: オーバーロード関数の呼び出しを解決することができません。(新機能 ; ヘルプを参照)
t.cpp(7): note: ‘void f(uint64_t)’ の可能性があります
t.cpp(6): note: または ‘void f(uint32_t)’
t.cpp(5): note: または ‘void f(uint16_t)’
t.cpp(4): note: または ‘void f(uint8_t)’
t.cpp(12): note: 引数リスト ‘(DWORD)’ を一致させようとしているとき

cstdintの中のstdint.hとwindows.hを見て原因を突き止めました。

  • uint32_tはunsigned intからのtypedef。
  • 一方、DWORDはunsigned longからのtypedef。

どうやら、コンパイラはunsigned longをunsigned intにするかunsigned long longにするか、決められなかったということのようです。

このように、整数型を大きさごとに多重定義するのは難しいようです。

この記事のカテゴリ

  • ⇒ サイズ別に整数型を多重定義したかった
  • ⇒ サイズ別に整数型を多重定義したかった

この記事は、C++ Advent Calendar 2016の12日目の記事です。


std::lock_guardクラステンプレートは、std::mutexなどのlockとunlockをRAII化するものとして欠かせません。

std::mutex m;
std::lock_guard<std::mutex> guard(m);

しかし、このようにテンプレート実引数としてミューテックスの型を指定しなければならないのが少しです。そこで、Boost.Thraedの中からこの点少し便利になったものを2つ紹介します。どれもヘッダーのみ、ビルド不要で使えます。

make_lock_guard

まずは、lock_guardオブジェクトを作るboost::make_lock_guard関数テンプレートです。std::make_pairなどのように、よくある関数テンプレートです。ただし、ミューテックスやlock guardは要件(コンセプト)でコピー・ムーブ可能となっていないので、このように参照で受ける必要があります。

std::mutex m;
auto&& guard = boost::make_lock_guard(m);
// 以下も可。
// const auto& guard = boost::make_lock_guard(m);

なお、boost::make_unique_lockとboost::make_unique_locksもあります。boost::make_unique_locksは複数個のミューテックスを一挙にロックします。内部では、boost::lock関数を呼び出しています。

std::mutex m1
std::recursive_mutex m2;
auto locks = boost::make_unique_locks(m1, m2);
// locksの型はstd::tuple<std::mutex, std::recursive_mutex>

なお、boost::adopt_lockを実引数として受け取るboost::make_lock_guardや、boost::adopt_lock, boost::defer_lock, boost::try_to_lockのそれぞれを実引数として受け取るboost::make_unique_lockもあります。

以下、Boostのリファレンスページへのリンクです。

with_lock_guard

次は、boost::with_lock_guard関数テンプレートです。これは、ミューテックスをロックしてから、指定の関数オブジェクトを呼び出すというものです。もちろん、関数オブジェクトから脱出したら、ロック解除されます。

std::mutex m;
int x = boost::with_lock_guard(m, [&]{
  // ロックされている状態。
  // いろんな処理を実行する。
  return 1;
});
// xには関数オブジェクトの戻り値1が入る。
 
boost::with_lock_guard(m, [](int n) {}, 2);
// with_lock_guardの3番目以降の実引数は、関数オブジェクトに渡される。

boost::with_lock_guardの良いところは、排他制御する範囲を明確にできる点です。戻り値の受け渡しが可能なので、関数内に{}でスコープを作ってstd::lock_guardを使うコードでは代用が難しい場合でも、スマートに書ける可能性があります。

class hoge
{
public:
  // デフォルトコンストラクタがないクラス。
  explicit hoge(int) {}
  hoge(hoge&&) = default;
};
 
std::mutex m;
 
void f1()
{
  // ここで前処理
 
  hoge obj = boost::with_lock_guard(m, []
  {
    hoge tmp(3);
    // ここで何か処理
    return tmp;
  });
 
  // ここで残りの処理
}
 
// こういうコードは書けない、という例
void f2()
{
  // ここで前処理
 
  hoge obj; // デフォルトコンストラクタがないので無理!
  {
    std::lock_guard<std::mutex> guard(m);
    hoge tmp(3);
    // ここで何か処理
    obj = std::move(tmp);
  };
 
  // ここで残りの処理
}

boost::with_lock_guardを使うには、<boost/thread/with_lock_guard.hpp>をインクルードします。

以下、Boostのリファレンスページへのリンクです。


なお、boost::make_lock_guardは、C++1zで不要になります(参考:C++1z クラステンプレートのテンプレート引数推論 – Faith and Brave – C++で遊ぼう)。

今回は、ロックの補助関数に焦点を当てることにしました。そのため、synchronized_value(boost::synchronized_valueクラス – yohhoyの日記)は取り上げていません。

std::lock_guardだけでなく、これらも使って、楽してコードを書いていきましょう。

この記事のカテゴリ

  • ⇒ Boost.Threadの排他制御を補助する関数

この記事は、C言語 Advent Calendar 2016の11日目の記事です。


前回の記事、十六進数で文字列に変換する (Boostのhex関数)では、uint32_t型の変数xを定義し、&x + 1というポインタ演算を行うC++のコードを載せました。xは配列でもなんでもありません。果たしてこんなことして良いのでしょうか、というのが今回の話です。Cにも当てはまる話なので、C言語 Advent Calendarに乗っかせていただくことにしました。

結論を述べると、問題ありません。C11ドラフトであるISO/IEC 9899:201x – n1548.pdfの6.5.6 Additive operatorsの7に、1要素の配列と同じよう取り扱う旨が書かれています。なお、Additive operatorsという見出しですが、2項演算子の+と-両方について書かれているところです。

For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

なお、C++でも同様の規定があります。C++14ドラフトのC++ International Standard – n4296.pdfでは、5.3.1 Unary operators [expr.unary.op]の3の途中に、以下の文章があります。

For purposes of pointer arithmetic (5.7) and comparison (5.9, 5.10), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T.

このように、CおよびC++でそれぞれ、&x + 1は、配列の最後の要素の次を指すポインタと同様、*で参照剥がしさえしなければ問題ありません。

こんなコードは全然問題ないわけです。

#include <stdio.h>
 
void test1() // 例
{
  int x = 500;
  int* end = &x + 1;
  for (int* p = &x; p != end; p++)
  {
    printf("%d\n", *p);
  }
}
 
void test2() // 比較用に配列を使ったもの
{
  int a[] = {
    1,
    2,
    3,
  };
  int* end = a + sizeof a / sizeof a[0];
  for (int* p = a; p != end; p++)
  {
    printf("%d\n", *p);
  }
}
 
int main()
{
  test1();
  puts("--------");
  test2();
}

ちなみに、Stack overflowにもIs the “one-past-the-end” pointer of a non-array type a valid concept in C++?という質問がありました。

というわけで、C++のコードを書いていて気になったことですが、Cにも当てはまることだったので、C言語 Advent Calendarのネタといたしました。

この記事のカテゴリ

  • ⇒ 配列でないオブジェクトに対するポインタ演算
  • ⇒ 配列でないオブジェクトに対するポインタ演算

この記事は、初心者C++er Advent Calendar 2016の4日目の記事です。


C++で、数値を文字列に変換する関数といえば、まずはstd::to_string (cpprefjp)です。ところが、この関数は、十進法で結果が出てきます。ほかによく使うものと言えば、十六進法です。というわけで、十六進法で文字列に変換する方法の話です。

標準ライブラリにいい感じのものがないので、Boostに頼ります。使いますのは、hexという単純な名前の関数です。

#include <iostream>
#include <string>
#include <iterator>
#include <cstdint>
#include <boost/algorithm/hex.hpp>
 
int main()
{
  std::uint32_t x = 0xdeadbeaf;
  std::string s;
  boost::algorithm::hex(&x, &x + 1, std::back_inserter(s));
  std::cout << s << std::endl;
}

このプログラムを実行すると、変数sの中身は”DEADBEAF”となり、それが出力されます。

hex関数は、「イテレータ2つまたはRangeで複数要素を指定し、一気に変換する関数」という設計になっています。ところが、このように1要素だけ変換するのに使うこともできるんです。

というわけで、boost::algorithm::hex関数を使い、数値オブジェクト1要素を文字列に変換する、という内容でした。こういう使い方、意外と見かけなかったので、今回紹介することにしました。

リファレンス: The Boost Algorithm Library – hex

この記事のカテゴリ

  • ⇒ 十六進数で文字列に変換する (Boostのhex関数)

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とを変換する

std::wstring_convertとstd::wstring_bufferって、std::localeと組み合わて使うのが難しいです。そんなわけで、その名前とは裏腹にstd::wstring (wchar_t)との組み合わせには使いづらいなあと思っています。

が、しかし、Visual C++はその辺、規格をいい感じにやっちゃって(無視して)いて、使えるようになっていることに気付いてしまいました。

このコード、コンパイルできてしまいました。もちろん、実行すればちゃんと動きます。

#include <codecvt>
#include <locale>
#include <string>
#include <iostream>
 
int main()
{
  using codecvt_wchar = std::codecvt<wchar_t, char, std::mbstate_t>;
 
  std::locale loc("");
  std::wstring_convert<codecvt_wchar> cv(
    &std::use_facet<codecvt_wchar>(loc));
 
  std::string message = "あいうえお";
 
  std::wstring wcs = cv.from_bytes(message);
  std::string mbs = cv.to_bytes(wcs);
 
  std::cout << mbs << std::endl;
}

wbuffer_convertも同様にコンパイルできました。

#include <codecvt>
#include <locale>
#include <string>
#include <iostream>
#include <sstream>
 
int main()
{
  using codecvt_wchar = std::codecvt<wchar_t, char, std::mbstate_t>;
 
  std::locale loc("");
 
  std::wstring_convert<codecvt_wchar> cv(
    &std::use_facet<codecvt_wchar>(loc));
 
  std::wbuffer_convert<codecvt_wchar> buf(
    std::cout.rdbuf(),
    &std::use_facet<codecvt_wchar>(loc));
  std::wostream my_wcout(&buf);
 
  std::wstring wcs = cv.from_bytes("かきくけこ");
  my_wcout << wcs << std::endl;
}

「こういう風に書くとダメなんですよー」という例を出すつもりで書き始めたら、逆にコンパイルエラーが出なくて、驚きました。

  • wstring_convertおよびwstring_bufferのコンストラクタがコンパイルできている。標準規格では、仮引数の型が非constなポインタ型となっているのに、VC++ではconstなポインタ型となっている。use_facetの戻り値の型はconstな参照なのだが、そのせいでコンパイルが通る。
  • wstring_convertおよびwstring_bufferのデストラクタがコンパイルできている。デストラクタでは、コンストラクタで渡したcodecvtへのポインタをdeleteすることになっているのだが、std::codecvtのデストラクタはprotected。VC++のwstring_convertとwbuffer_convertは、内部でstd::localeオブジェクトにcodecvtを持たせることで、この問題を回避している。

GCC (libstdc++)やClang (libc++)だといずれもコンパイルエラーです。

この実装、私がこういう風に使えたら良かったと考えていた感じです。その点では素晴らしいのですが、現実VC++だけが標準規格に沿っていないという現状では、あまり有用に使えないのですよね。

2017年1月15日追記: 後者について、開発元に質問が出ているのを見かけています: [Visual C++] Is it accepted std::wstring_convert/std::wbuffer_convert delete std::codecvt pointer ? – Developer Community

この記事のカテゴリ

  • ⇒ VC++のwstring_convertやwbuffer_convertがちょっと変
  • ⇒ VC++のwstring_convertやwbuffer_convertがちょっと変

次ページへ »