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でリンクエラーになる問題の回避策