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);
} |
#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);
} |
#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++ ⇒ VC++ 2015のcodecvtでリンクエラーになる問題の回避策