マルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換を行うライブラリを作りました(SJIS, UTF-8, UTF-16に対応。SJIS⇔UTF-8の変換も可能)

C++でWindowsアプリを作る場合に、マルチバイト文字列(std::string)とワイド文字列(std::wstring)の間で変換しなければならないことがあります。今回は、Windows API の MultiByteToWideChar() と WideCharToMultiByte() を使って変換を行うライブラリ(ヘッダーオンリー)を作ってみました(文末の strconv2.h を保存してお使いください。C++専用です。Cでは使えません)。また、内部的にワイド文字列を経由することで シフトJIS⇔UTF-8 の変換(マルチバイト文字列同士の変換)を行う関数も用意しています。

<!--
* <b>C++11 以降</b>のコンパイラとC++11 以前(<b>C++98, C++03</b>)のコンパイラの両方で動作するように工夫してあります(但し、ご利用になる場合にはコンパイラのバージョンを気にする必要はありません)。
-->

* <b>UTF-16/UTF-32対応をいれたので、C++98で使えなくなりました。C++98のコンパイラで使いたい場合は [strconv.h](https://github.com/javacommons/strconv/blob/master/strconv.h) をお使いください。[strconv.h](https://github.com/javacommons/strconv/blob/master/strconv.h) の方も引き続きサポートします。</b>
* <b>このライブラリは多くの企業／デベロッパーで利用されています。</b>
* <b>Visual C++, MinGW の 32bit/64bit 版でコンパイル・動作を確認しています。</b>
* 添付の strconv2.h のライセンスを <b>MIT License</b> or Public Domain (<b>Unlicense</b>) のデュアルライセンスにしました。[詳しくはこちらをご覧ください。](https://qiita.com/javacommons/items/9ea0c8fd43b61b01a8da#%E3%81%93%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9-mit-license-or-public-domain)
Public Domain (<b>Unlicense</b>) に関しては https://ja.wikipedia.org/wiki/Unlicense もご覧ください。

# 使用例1(ライブラリをそのまま使用)

```cpp:main1.cpp
#include <windows.h>
#include "strconv2.h"

int main(int argc, char **argv)
{
  std::string utf8_str = u8"あいう";
  printf("utf8_str.length()=%u\n", utf8_str.length());
  std::wstring wide_str = utf8_to_wide(utf8_str);
  printf("wide_str.length()=%u\n", wide_str.length());
  std::string sjis_str = wide_to_sjis(wide_str);
  printf("sjis_str.length()=%u\n", sjis_str.length());
  printf("sjis_str.c_str()=%s\n", sjis_str.c_str());
  return 0;
}

```

```:実行結果
>main1.exe
utf8_str.length()=9
wide_str.length()=3
sjis_str.length()=6
sjis_str.c_str()=あいう
```

# 使用例2(メッセージをコード変換して出力)

使用例3とちがって、formatA の出力は、(日本語Windowsでは)シフトJISにしか対応していません。(chcp 65001 を実行すると文字化けします)
※ formatを使って韓国語などを表示したい(chcp 65001 に対応したい)場合は、
    `unicode_ostream aout(std::cout, GetConsoleOutputCP()); aout << format(u8"漢字=%s", u8"한자") << std::endl;`
のようにしてください。unicode_ostream と一緒に用いる場合は formatA() ではなくて format() を用いてください。

```cpp:main2.cpp
#include "strconv2.h"
#include <iostream>
#include <string>

int main(void)
{
    std::string nameUtf8 = u8"太郎";
    int age = 15;
    // 以下の3行は同じ意味です。
    std::cout << utf8_to_ansi(format(u8"ハロー、私の名前は %s。 年は %d だ!", nameUtf8.c_str(), age)) << std::endl;
    std::cout << formatA(u8"ハロー、私の名前は %s。 年は %d だ!", nameUtf8.c_str(), age) << std::endl;
    formatA(std::cout, u8"ハロー、私の名前は %s。 年は %d だ!\n", nameUtf8.c_str(), age);
    std::wstring nameWide = L"花子";
    age = 23;
    // 以下の3行は同じ意味です。
    std::cout << wide_to_ansi(format(L"ハロー、私の名前は %s。 年は %d だ!", nameWide.c_str(), age)) << std::endl;
    std::cout << formatA(L"ハロー、私の名前は %s。 年は %d だ!", nameWide.c_str(), age) << std::endl;
    formatA(std::cout, L"ハロー、私の名前は %s。 年は %d だ!\n", nameWide.c_str(), age);
    return 0;
}
```

```:実行結果(日本語版WindowsではシフトJISで出力されます)
>main2.exe
ハロー、私の名前は 太郎。 年は 15 だ!
ハロー、私の名前は 太郎。 年は 15 だ!
ハロー、私の名前は 太郎。 年は 15 だ!
ハロー、私の名前は 花子。 年は 23 だ!
ハロー、私の名前は 花子。 年は 23 だ!
ハロー、私の名前は 花子。 年は 23 だ!
```

# 使用例3(メッセージをコード変換して出力(std::cout互換＝型安全な方法))

```cpp:main3.cpp
#include "strconv2.h"
#include <iostream>
#include <iomanip>
#include <cmath>

using namespace std;

class CMyClass
{
    time_t t;
    int count;

public:
    CMyClass()
    {
        this->t = std::time(nullptr);
        this->count = 0;
    }
    int CountUp()
    {
        return ++this->count;
    }
    friend unicode_ostream &operator<<(unicode_ostream &stream, const CMyClass &value);
};

unicode_ostream &operator<<(unicode_ostream &stream, const CMyClass &value)
{
    struct tm *ptm = std::localtime(&value.t);
    stream << u8"[作成日時=" << std::put_time(ptm, "%Y-%m-%d %H:%M:%S");
    stream << u8"、カウント=" << value.count << "]";
    return stream;
};

int main()
{
#ifdef ANSI_ONLY
    unicode_ostream aout(cout);
#else
    unicode_ostream aout(cout, GetConsoleOutputCP()); // chcp 65001 とすると「한자」が「??」に化けずに表示される
#endif
    CMyClass mc;
    mc.CountUp();
    mc.CountUp();
    aout << mc << endl;
    double pi = 4 * atan(1.0);
    aout << u8"π(1)=" << pi << endl;
    aout << u8"π(2)=" << format("%.2f", pi) << endl;

    aout << 1 << u8" char*漢字=한자 " << std::string(u8" string漢字=한자 ") << 1.2345 << endl;
    aout << 2 << L" wchar_t*漢字=한자 " << std::wstring(L" wstring漢字=한자 ") << 1.2345 << endl;

    double A = 100;
    double B = 2001.5251;

    // 書式指定(A) hex の代わりに setbase(16) を使うこともできます
    aout << hex << left << showbase << nouppercase;
    // 実際の印字処理(A)
    aout << (long long)A << endl;

    // 書式指定(B) setbase(10) の代わりに dec を使うこともできます
    aout << setbase(10) << right << setw(15)
         << setfill('_') << showpos
         << fixed << setprecision(2);
    // 実際の印字処理(B)
    aout << B << endl;

    return 0;
}
```

chcp 65001 を試すときは、生の PowerShell では漢字や韓国語が表示されませんので、コマンドプロンプトか Windows Terminal をお使いください。Windows Terminal の PowerShell では chcp 65001 しても、漢字や韓国語が問題なく表示されます。

```text:実行結果
>chcp 932
>main3.exe
[作成日時=2020-12-25 21:17:18、カウント=2]
π(1)=3.14159
π(2)=3.14
1 char*漢字=??  string漢字=?? 1.2345
2 wchar_t*漢字=??  wstring漢字=?? 1.2345
0x64
_______+2001.53

>chcp 65001
>main3.exe
[作成日時=2020-12-25 21:20:44、カウント=2]
π(1)=3.14159
π(2)=3.14
1 char*漢字=한자  string漢字=한자 1.2345
2 wchar_t*漢字=한자  wstring漢字=한자 1.2345
0x64
_______+2001.53

```

# 使用例4(UTF-16, UTF-32との変換)

```cpp:main4.cpp
#include "strconv2.h"

int main(int argc, char *argv[])
{
    unicode_ostream uout(std::cout, GetConsoleOutputCP());

    std::string utf8 = U8("ユーティーエフ8");
    uout << utf8_to_char16(utf8) << std::endl;
    uout << utf8_to_char32(utf8) << std::endl;

    uout << u"ゆーてぃーえふ16" << std::endl;
    uout << U"ゆーてぃーえふ32" << std::endl;

    std::u16string u16s = U16("ゆー16");
    std::u32string u32s = U32("ゆー32");

    uout << char16_to_utf8(u16s) << std::endl;
    uout << char32_to_utf8(u32s) << std::endl;

    std::vector<unsigned int> v = {
        437,    // OEM 米国
        737,    // ギリシャ語
        775,    // バルト言語
        850,    // 西ヨーロッパ言語
        852,    // 中央ヨーロッパ言語
        855,    // OEM キリル
        857,    // トルコ語
        858,    // OEM マルチリンガル ラテン I
        860,    // ポルトガル語
        861,    // アイスランド語
        862,    // ヘブライ語
        863,    // フランス語 (カナダ)
        864,    // アラビア語
        865,    // 北欧
        866,    // キリル言語
        869,    // ギリシャ語, Modern
        874,    // タイ語
        932,    // 日本語 (シフト JIS)
        936,    // 簡体字中国語 (GB2312)
        949,    // 韓国語
        950,    // 繁体字中国語 (Big5)
        1250,   // 中央ヨーロッパ言語
        1251,   // キリル言語
        1252,   // 西ヨーロッパ言語
        1253,   // ギリシャ語
        1254,   // トルコ語
        1255,   // ヘブライ語
        1256,   // アラビア語
        1257,   // バルト言語
        1258,   // ベトナム語
        1361,   // 韓国語 (Johab)
        65001,  // Unicode (UTF-8)
    };
    std::string s = U8("abc");
    int error_count = 0;
    for (std::size_t i = 0; i < v.size(); i++)
    {
        std::string s2 = utf8_to_cp(s, v[i]);
        if (s2 == s) continue;
        uout << "ERROR: " << v[i] << ":" << utf8_to_cp(s, v[i]) << std::endl;
        error_count++;
    }
    uout << U8("変換エラー: ") << error_count << U8("個") << std::endl;

    return 0;
}
```

```text:実行結果
ユーティーエフ8
ユーティーエフ8
ゆーてぃーえふ16
ゆーてぃーえふ32
ゆー16
ゆー32
変換エラー: 0個
```

# C++20への対応

C++20(モード)でコンパイルすると、`std::string s = u8"あいうえお";` の部分がエラーになります。
逆にこの部分だけ対処すればソースコードの互換性、および動作の互換性を保証することができます。
**また、明示的に std::u8string を使う場合には、format(), formatA(), unicode_ostreamクラス等も std::u8string に対応しています。**

```cpp:C++20でエラーになるソースコード
#include "strconv2.h"
int main(int argc, char *argv[])
{
    std::string s = u8"あいうえお";
    formatA(std::cout, "%s\n", s.c_str());
    return 0;
}
```

```cpp:対処方法1(確実な方法)
#include "strconv2.h"
int main(int argc, char *argv[])
{
    // std::string s = (const char *)u8"あいうえお"; に展開されるマクロ
    std::string s = U8("あいうえお");
    formatA(std::cout, "%s\n", s.c_str());
    return 0;
}
```

```cpp:対処方法2(MinGWのgccなどで文字コードが元々utf-8な場合)
#include "strconv2.h"
int main(int argc, char *argv[])
{
    std::string s = "あいうえお"; // u8 を取り除く
    formatA(std::cout, "%s\n", s.c_str());
    return 0;
}
```

```cpp:対処方法3(char8_t *, std::u8string に対応した関数を使う⇒ソース非互換になる)
#include "strconv2.h"
int main(int argc, char *argv[])
{
    std::u8string s = u8"あいうえお";
    formatA(std::cout, u8"%s\n", s.c_str());
    std::wstring ws = char8_to_wide(s);
    formatA(std::cout, L"%s\n", ws.c_str());
    return 0;
}
```

strconv2.h には以下の関数があり、char8_t *, std::u8string と std::string/std::wstring の間の変換ができるようになっています。

* static inline std::u8string utf8_to_char8(const std::string &s)
* static inline std::string char8_to_utf8(const std::u8string &s)
* static inline std::wstring char8_to_wide(const std::u8string &s)
* static inline std::u8string wide_to_char8(const std::wstring &s)
* static inline std::u8string ansi_to_char8(const std::string &s)
* static inline std::string char8_to_ansi(const std::u8string &s)
* static inline std::u8string sjis_to_char8(const std::string &s)
* static inline std::string char8_to_sjis(const std::u8string &s)

# 関数一覧

## std::wstring ansi_to_wide(const std::string &s)

システムロケールにおける既定のコードページの文字コードからワイド文字列への変換を行います。システムロケールが「日本/日本語」の場合は sjis_to_wide() と同じ結果となります。

## std::string wide_to_ansi(const std::wstring &s)

ワイド文字列からシステムロケールにおける既定のコードページの文字コードへの変換を行います。システムロケールが「日本/日本語」の場合は wide_to_sjis() と同じ結果となります。

## std::wstring sjis_to_wide(const std::string &s)

シフトJIS(コードページ932)からワイド文字列への変換を行います。

## std::string wide_to_sjis(const std::wstring &s)

ワイド文字列からシフトJIS(コードページ932)への変換を行います。

## std::wstring utf8_to_wide(const std::string &s)

UTF-8からワイド文字列への変換を行います。

## std::string wide_to_utf8(const std::wstring &s)

ワイド文字列からUTF-8への変換を行います。

## std::string ansi_to_utf8(const std::string &s)

システムロケールにおける既定のコードページの文字コードからUTF-8への変換を行います。システムロケールが「日本/日本語」の場合は sjis_to_utf8() と同じ結果となります。

## std::string utf8_to_ansi(const std::string &s)

UTF-8からシステムロケールにおける既定のコードページの文字コードへの変換を行います。システムロケールが「日本/日本語」の場合は utf8_to_sjis() と同じ結果となります。

## std::string sjis_to_utf8(const std::string &s)

シフトJIS(コードページ932)からUTF-8への変換を行います。

## std::string utf8_to_sjis(const std::string &s)

UTF-8からシフトJIS(コードページ932)への変換を行います。

## std::string format(const char *format, ...)

printf()の書式指定に従って std::string を出力します。sprintf()のようなものです。(主に UTF-8 の入力を想定していますが、シフトJISで使っていただいても構いません)

## std::wstring format(const wchar_t *format, ...)

wprintf()の書式指定に従って std::wstring を出力します。wide版sprintf()のようなものです。(こちらはワイド文字列用です)

## std::string formatA(const char *format, ...)

上記format()関数の出力をANSI(日本語WindowsではシフトJIS)に変換して返します。そのまま std::cout への出力に使えます。(但し、入力はUTF-8を想定しています←utf8_to_ansi()を適用しているため)

```cpp
    // 以下の3行は同じ意味です。
    std::cout << utf8_to_ansi(format(u8"ハロー、私の名前は %s。 年は %d だ!", nameUtf8.c_str(), age)) << std::endl;
    std::cout << formatA(u8"ハロー、私の名前は %s。 年は %d だ!", nameUtf8.c_str(), age) << std::endl;
    formatA(std::cout, u8"ハロー、私の名前は %s。 年は %d だ!\n", nameUtf8.c_str(), age);
```

## std::string formatA(const wchar_t *format, ...)

上記format()関数の出力をANSI(日本語WindowsではシフトJIS)に変換して返します。そのまま std::cout への出力に使えます。(ワイド文字列を入力とする関数です)

```cpp
    // 以下の3行は同じ意味です。
    std::cout << wide_to_ansi(format(L"ハロー、私の名前は %s。 年は %d だ!", nameWide.c_str(), age)) << std::endl;
    std::cout << formatA(L"ハロー、私の名前は %s。 年は %d だ!", nameWide.c_str(), age) << std::endl;
    formatA(std::cout, L"ハロー、私の名前は %s。 年は %d だ!\n", nameWide.c_str(), age);
```

## char8_t *, std::u8string と std::string/std::wstring の間の変換用

static inline std::u8string utf8_to_char8(const std::string &s)
static inline std::string char8_to_utf8(const std::u8string &s)
static inline std::wstring char8_to_wide(const std::u8string &s)
static inline std::u8string wide_to_char8(const std::wstring &s)
static inline std::u8string ansi_to_char8(const std::string &s)
static inline std::string char8_to_ansi(const std::u8string &s)
static inline std::u8string sjis_to_char8(const std::string &s)
static inline std::string char8_to_sjis(const std::u8string &s)


## UTF-16, UTF-32対応関数
* static inline std::string char16_to_utf8(const std::u16string &s)
* static inline std::u16string utf8_to_char16(const std::string &s)
* static inline std::string char32_to_utf8(const std::u32string &s)
* static inline std::u32string utf8_to_char32(const std::string &s)
// 以下C++20用
* static inline std::u8string char16_to_char8(const std::u16string &s)
* static inline std::u16string char8_to_char16(const std::u8string &s)
* static inline std::u8string char32_to_char8(const std::u32string &s)
* static inline std::u32string char8_to_char32(const std::u8string &s)

# 添付ファイル(strconv2.h)

* https://github.com/javacommons/strconv の方にもコード(strconv2.h)を置いています。この記事内には最新のコードを貼り付けておきます。

```cpp:strconv2.h
/* strconv2.h v1.0.1               */
/* Last Modified: 2025/04/10 17:16 */
#ifndef STRCONV2_H
#define STRCONV2_H

#include <stdarg.h>
#include <windows.h>

#include <codecvt>
#include <iostream>
#include <locale>
#include <sstream>
#include <string>
#include <vector>

#ifdef STRCONV_NLOHMANN
#include <nlohmann/json.hpp>
#endif

static inline std::wstring cp_to_wide(const std::string &s, UINT codepage)
{
    int in_length = (int)s.length();
    int out_length =
        MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0);
    std::wstring result(out_length, L'\0');
    if (out_length)
        MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &result[0],
                            out_length);
    return result;
}
static inline std::string wide_to_cp(const std::wstring &s, UINT codepage)
{
    int in_length = (int)s.length();
    int out_length =
        WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0);
    std::string result(out_length, '\0');
    if (out_length)
        WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &result[0],
                            out_length, 0, 0);
    return result;
}

static inline std::string cp_to_utf8(const std::string &s, UINT codepage)
{
    if (codepage == CP_UTF8) return s;
    std::wstring wide = cp_to_wide(s, codepage);
    return wide_to_cp(wide, CP_UTF8);
}
static inline std::string utf8_to_cp(const std::string &s, UINT codepage)
{
    if (codepage == CP_UTF8) return s;
    std::wstring wide = cp_to_wide(s, CP_UTF8);
    return wide_to_cp(wide, codepage);
}

static inline std::wstring ansi_to_wide(const std::string &s)
{
    return cp_to_wide(s, CP_ACP);
}
static inline std::string wide_to_ansi(const std::wstring &s)
{
    return wide_to_cp(s, CP_ACP);
}

static inline std::wstring sjis_to_wide(const std::string &s)
{
    return cp_to_wide(s, 932);
}
static inline std::string wide_to_sjis(const std::wstring &s)
{
    return wide_to_cp(s, 932);
}

static inline std::wstring utf8_to_wide(const std::string &s)
{
    return cp_to_wide(s, CP_UTF8);
}
static inline std::string wide_to_utf8(const std::wstring &s)
{
    return wide_to_cp(s, CP_UTF8);
}

static inline std::string ansi_to_utf8(const std::string &s)
{
    return cp_to_utf8(s, CP_ACP);
}
static inline std::string utf8_to_ansi(const std::string &s)
{
    return utf8_to_cp(s, CP_ACP);
}

static inline std::string sjis_to_utf8(const std::string &s)
{
    return cp_to_utf8(s, 932);
}
static inline std::string utf8_to_sjis(const std::string &s)
{
    return utf8_to_cp(s, 932);
}

#ifdef __cpp_char8_t
static inline std::u8string utf8_to_char8(const std::string &s)
{
    return std::u8string(s.begin(), s.end());
}
static inline std::string char8_to_utf8(const std::u8string &s)
{
    return std::string(s.begin(), s.end());
}

static inline std::wstring char8_to_wide(const std::u8string &s)
{
    return cp_to_wide(char8_to_utf8(s), CP_UTF8);
}
static inline std::u8string wide_to_char8(const std::wstring &s)
{
    return utf8_to_char8(wide_to_cp(s, CP_UTF8));
}

static inline std::u8string cp_to_char8(const std::string &s, UINT codepage)
{
    return utf8_to_char8(cp_to_utf8(s, codepage));
}
static inline std::string char8_to_cp(const std::u8string &s, UINT codepage)
{
    return utf8_to_cp(char8_to_utf8(s), codepage);
}

static inline std::u8string ansi_to_char8(const std::string &s)
{
    return cp_to_char8(s, CP_ACP);
}
static inline std::string char8_to_ansi(const std::u8string &s)
{
    return char8_to_cp(s, CP_ACP);
}

static inline std::u8string sjis_to_char8(const std::string &s)
{
    return cp_to_char8(s, 932);
}
static inline std::string char8_to_sjis(const std::u8string &s)
{
    return char8_to_cp(s, 932);
}
#endif

#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4996)
#endif

static inline std::string char16_to_utf8(const std::u16string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> convert;
        return convert.to_bytes(s);
    }
    catch (...)
    {
        return "";
    }
}

static inline std::u16string utf8_to_char16(const std::string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> convert;
        return convert.from_bytes(s);
    }
    catch (...)
    {
        return u"";
    }
}

static inline std::string char16_to_cp(const std::u16string &s,
                                       unsigned int codepage)
{
    return utf8_to_cp(char16_to_utf8(s), codepage);
}

static inline std::string char32_to_utf8(const std::u32string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
        return convert.to_bytes(s);
    }
    catch (...)
    {
        return "";
    }
}

static inline std::u32string utf8_to_char32(const std::string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
        return convert.from_bytes(s);
    }
    catch (...)
    {
        return U"";
    }
}

static inline std::string char32_to_cp(const std::u32string &s,
                                       unsigned int codepage)
{
    return utf8_to_cp(char32_to_utf8(s), codepage);
}

#ifdef __cpp_char8_t
static inline std::u8string char16_to_char8(const std::u16string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> convert;
        return utf8_to_char8(convert.to_bytes(s));
    }
    catch (...)
    {
        return u8"";
    }
}

static inline std::u16string char8_to_char16(const std::u8string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> convert;
        return convert.from_bytes(char8_to_utf8(s));
    }
    catch (...)
    {
        return u"";
    }
}

static inline std::u8string char32_to_char8(const std::u32string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
        return utf8_to_char8(convert.to_bytes(s));
    }
    catch (...)
    {
        return u8"";
    }
}

static inline std::u32string char8_to_char32(const std::u8string &s)
{
    try
    {
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
        return convert.from_bytes(char8_to_utf8(s));
    }
    catch (...)
    {
        return U"";
    }
}
#endif

static inline std::wstring vformat(const wchar_t *format, va_list args)
{
    int len = _vsnwprintf(0, 0, format, args);
    if (len < 0) return L"";
    std::vector<wchar_t> buffer(len + 1);
    len = _vsnwprintf(&buffer[0], len, format, args);
    if (len < 0) return L"";
    buffer[len] = L'\0';
    return &buffer[0];
}
static inline std::string vformat(const char *format, va_list args)
{
    int len = _vsnprintf(0, 0, format, args);
    if (len < 0) return "";
    std::vector<char> buffer(len + 1);
    len = _vsnprintf(&buffer[0], len, format, args);
    if (len < 0) return "";
    buffer[len] = '\0';
    return &buffer[0];
}
#ifdef __cpp_char8_t
static inline std::u8string vformat(const char8_t *format, va_list args)
{
    int len = _vsnprintf(0, 0, (const char *)format, args);
    if (len < 0) return u8"";
    std::vector<char> buffer(len + 1);
    len = _vsnprintf(&buffer[0], len, (const char *)format, args);
    if (len < 0) return u8"";
    buffer[len] = '\0';
    return (char8_t *)&buffer[0];
}
#endif

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

static inline std::wstring format(const wchar_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::wstring s = vformat(format, args);
    va_end(args);
    return s;
}
static inline std::string format(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    std::string s = vformat(format, args);
    va_end(args);
    return s;
}
#ifdef __cpp_char8_t
static inline std::u8string format(const char8_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::u8string s = vformat(format, args);
    va_end(args);
    return s;
}
#endif

static inline void format(std::ostream &ostrm, const wchar_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::wstring s = vformat(format, args);
    va_end(args);
    ostrm << wide_to_utf8(s) << std::flush;
}
static inline void format(std::ostream &ostrm, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    std::string s = vformat(format, args);
    va_end(args);
    ostrm << s << std::flush;
}
#ifdef __cpp_char8_t
static inline void format(std::ostream &ostrm, const char8_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::u8string s = vformat(format, args);
    va_end(args);
    ostrm << char8_to_utf8(s) << std::flush;
}
#endif

static inline std::string formatA(const wchar_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::wstring s = vformat(format, args);
    va_end(args);
    return wide_to_ansi(s);
}
static inline std::string formatA(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    std::string s = vformat(format, args);
    va_end(args);
    return utf8_to_ansi(s);
}
#ifdef __cpp_char8_t
static inline std::string formatA(const char8_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::u8string s = vformat(format, args);
    va_end(args);
    return char8_to_ansi(s);
}
#endif

static inline void formatA(std::ostream &ostrm, const wchar_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::wstring s = vformat(format, args);
    va_end(args);
    ostrm << wide_to_ansi(s) << std::flush;
}
static inline void formatA(std::ostream &ostrm, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    std::string s = vformat(format, args);
    va_end(args);
    ostrm << utf8_to_ansi(s) << std::flush;
}
#ifdef __cpp_char8_t
static inline void formatA(std::ostream &ostrm, const char8_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::u8string s = vformat(format, args);
    va_end(args);
    ostrm << char8_to_ansi(s) << std::flush;
}
#endif

static inline void dbgmsg(const wchar_t *title, const wchar_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::wstring s = vformat(format, args);
    va_end(args);
    MessageBoxW(0, s.c_str(), title, MB_OK);
}
static inline void dbgmsg(const char *title, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    std::string s = vformat(format, args);
    va_end(args);
    MessageBoxW(0, utf8_to_wide(s).c_str(), utf8_to_wide(title).c_str(), MB_OK);
}
#ifdef __cpp_char8_t
static inline void dbgmsg(const char8_t *title, const char8_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::u8string s = vformat(format, args);
    va_end(args);
    MessageBoxW(0, char8_to_wide(s).c_str(), char8_to_wide(title).c_str(),
                MB_OK);
}
#endif

static inline HANDLE handle_for_ostream(std::ostream &ostrm)
{
    if (&ostrm == &std::cout)
    {
        return GetStdHandle(STD_OUTPUT_HANDLE);
    }
    else if (&ostrm == &std::cerr)
    {
        return GetStdHandle(STD_ERROR_HANDLE);
    }
    return INVALID_HANDLE_VALUE;
}
static inline void dbgout(std::ostream &ostrm, const wchar_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::wstring ws = vformat(format, args);
    va_end(args);
    HANDLE h = handle_for_ostream(ostrm);
    if (h == INVALID_HANDLE_VALUE)
    {
        return;
    }
    DWORD dwNumberOfCharsWrite;
    if (GetFileType(h) != FILE_TYPE_CHAR)
    {
        std::string s = wide_to_cp(ws, GetConsoleOutputCP());
        WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL);
    }
    else
    {
        WriteConsoleW(h, ws.c_str(), (DWORD)ws.size(), &dwNumberOfCharsWrite,
                      NULL);
    }
}
static inline void dbgout(std::ostream &ostrm, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    std::string s = vformat(format, args);
    va_end(args);
    HANDLE h = handle_for_ostream(ostrm);
    if (h == INVALID_HANDLE_VALUE)
    {
        return;
    }
    DWORD dwNumberOfCharsWrite;
    if (GetFileType(h) != FILE_TYPE_CHAR)
    {
        s = utf8_to_cp(s, GetConsoleOutputCP());
        WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL);
    }
    else
    {
        std::wstring ws = utf8_to_wide(s);
        WriteConsoleW(h, ws.c_str(), (DWORD)ws.size(), &dwNumberOfCharsWrite,
                      NULL);
    }
}
#ifdef __cpp_char8_t
static inline void dbgout(std::ostream &ostrm, const char8_t *format, ...)
{
    va_list args;
    va_start(args, format);
    std::u8string s = vformat(format, args);
    va_end(args);
    HANDLE h = handle_for_ostream(ostrm);
    if (h == INVALID_HANDLE_VALUE)
    {
        return;
    }
    DWORD dwNumberOfCharsWrite;
    if (GetFileType(h) != FILE_TYPE_CHAR)
    {
        std::string str = char8_to_cp(s, GetConsoleOutputCP());
        WriteFile(h, (const char *)str.c_str(), (DWORD)str.size(),
                  &dwNumberOfCharsWrite, NULL);
    }
    else
    {
        std::wstring ws = char8_to_wide(s);
        WriteConsoleW(h, ws.c_str(), (DWORD)ws.size(), &dwNumberOfCharsWrite,
                      NULL);
    }
}
#endif

class unicode_ostream
{
private:
    std::ostream *m_ostrm;
    UINT m_target_cp;
    bool is_ascii(const std::string &s)
    {
        for (std::size_t i = 0; i < s.size(); i++)
        {
            unsigned char c = (unsigned char)s[i];
            if (c > 0x7f) return false;
        }
        return true;
    }

public:
    unicode_ostream(std::ostream &ostrm, UINT target_cp = CP_ACP)
        : m_ostrm(&ostrm), m_target_cp(target_cp)
    {
    }
    std::ostream &stream() { return *m_ostrm; }
    void stream(std::ostream &ostrm) { m_ostrm = &ostrm; }
    UINT target_cp() { return m_target_cp; }
    void target_cp(UINT cp) { m_target_cp = cp; }
    template <typename T>
    unicode_ostream &operator<<(const T &x)
    {
        std::ostringstream oss;
        oss << x;
        std::string output = oss.str();
        if (is_ascii(output))
        {
            (*m_ostrm) << x;
        }
        else
        {
            (*m_ostrm) << utf8_to_cp(output, m_target_cp);
        }
        return *this;
    }
    unicode_ostream &operator<<(const std::wstring &x)
    {
        (*m_ostrm) << wide_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const wchar_t *x)
    {
        (*m_ostrm) << wide_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const std::string &x)
    {
        (*m_ostrm) << utf8_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const char *x)
    {
        (*m_ostrm) << utf8_to_cp(x, m_target_cp);
        return *this;
    }
#ifdef __cpp_char8_t
    unicode_ostream &operator<<(const std::u8string &x)
    {
        (*m_ostrm) << char8_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const char8_t *x)
    {
        (*m_ostrm) << char8_to_cp(x, m_target_cp);
        return *this;
    }
#endif
    unicode_ostream &operator<<(const std::u16string &x)
    {
        (*m_ostrm) << char16_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const char16_t *x)
    {
        (*m_ostrm) << char16_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const std::u32string &x)
    {
        (*m_ostrm) << char32_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(const char32_t *x)
    {
        (*m_ostrm) << char32_to_cp(x, m_target_cp);
        return *this;
    }
    unicode_ostream &operator<<(
        std::ostream &(*pf)(std::ostream &))  // For manipulators...
    {
        (*m_ostrm) << pf;
        return *this;
    }
    unicode_ostream &operator<<(std::basic_ios<char> &(*pf)(
        std::basic_ios<char> &))  // For manipulators...
    {
        (*m_ostrm) << pf;
        return *this;
    }
#ifdef STRCONV_NLOHMANN
    unicode_ostream &operator<<(const nlohmann::json &x)
    {
        std::string j = x.dump();
        (*m_ostrm) << utf8_to_cp(j, m_target_cp);
        return *this;
    }
#endif
};

#define U8(X) ((const char *)u8##X)
#define U16(X) (u##X)
#define U32(X) (U##X)
#define WIDE(X) (L##X)

#endif /* STRCONV2_H */
```

# 関数一覧にないコードページへの対応方法

以下の関数の UINT codepage 引数に文末の表のコードページを渡すことで様々な文字コード(日本語EUC・JIS等も含む)に対応することができます。関数一覧に挙げた関数の実際の定義を参考にして、以下の関数をラップする関数を作るのが良いかもしれません。便利なら、`strconv.h` の末尾にそれらの関数を付け加えても構いません。UINT codepage に 932 を渡している、sjis_to_～() や ～_to_sjis() 関数が参考になるでしょう。932 は文末のコードページ一覧表に「日本語 (シフト JIS)」として載っています。

* std::wstring cp_to_wide(const std::string &s, UINT codepage)
* std::string wide_to_cp(const std::wstring &s, UINT codepage)
* std::string cp_to_utf8(const std::string &s, UINT codepage)
* std::string utf8_to_cp(const std::string &s, UINT codepage)

<details><summary>(コードページ一覧表。長いので折りたたみ中。展開してご覧ください)</summary><div>

|コードページ|説明|
|:--|:--|
|37|IBM EBCDIC (米国 - カナダ)|
|437|OEM 米国|
|500|IBM EBCDIC (インターナショナル)|
|708|アラビア語 (ASMO 708)|
|720|アラビア語 (DOS)|
|737|ギリシャ語 (DOS)|
|775|バルト言語 (DOS)|
|850|西ヨーロッパ言語 (DOS)|
|852|中央ヨーロッパ言語 (DOS)|
|855|OEM キリル|
|857|トルコ語 (DOS)|
|858|OEM マルチリンガル ラテン I|
|860|ポルトガル語 (DOS)|
|861|アイスランド語 (DOS)|
|862|ヘブライ語 (DOS)|
|863|フランス語 (カナダ) (DOS)|
|864|アラビア語 (864)|
|865|北欧 (DOS)|
|866|キリル言語 (DOS)|
|869|ギリシャ語, Modern (DOS)|
|870|IBM EBCDIC (多国語ラテン 2)|
|874|タイ語 (Windows)|
|875|IBM EBCDIC (ギリシャ語 Modern)|
|932|日本語 (シフト JIS)|
|936|簡体字中国語 (GB2312)|
|949|韓国語|
|950|繁体字中国語 (Big5)|
|1026|IBM EBCDIC (トルコ語ラテン 5)|
|1047|IBM ラテン-1|
|1140|IBM EBCDIC (米国 - カナダ - ヨーロッパ)|
|1141|IBM EBCDIC (ドイツ - ヨーロッパ)|
|1142|IBM EBCDIC (デンマーク - ノルウェー - ヨーロッパ)|
|1143|IBM EBCDIC (フィンランド - スウェーデン - ヨーロッパ)|
|1144|IBM EBCDIC (イタリア - ヨーロッパ)|
|1145|IBM EBCDIC (スペイン - ヨーロッパ)|
|1146|IBM EBCDIC (英国 - ヨーロッパ)|
|1147|IBM EBCDIC (フランス - ヨーロッパ)|
|1148|IBM EBCDIC (インターナショナル - ヨーロッパ)|
|1149|IBM EBCDIC (アイスランド語 - ヨーロッパ)|
|1200|Unicode|
|1201|Unicode (ビッグ エンディアン)|
|1250|中央ヨーロッパ言語 (Windows)|
|1251|キリル言語 (Windows)|
|1252|西ヨーロッパ言語 (Windows)|
|1253|ギリシャ語 (Windows)|
|1254|トルコ語 (Windows)|
|1255|ヘブライ語 (Windows)|
|1256|アラビア語 (Windows)|
|1257|バルト言語 (Windows)|
|1258|ベトナム語 (Windows)|
|1361|韓国語 (Johab)|
|10000|西ヨーロッパ言語 (Mac)|
|10001|日本語 (Mac)|
|10002|繁体字中国語 (Mac)|
|10003|韓国語 (Mac)|
|10004|アラビア語 (Mac)|
|10005|ヘブライ語 (Mac)|
|10006|ギリシャ語 (Mac)|
|10007|キリル言語 (Mac)|
|10008|簡体字中国語 (Mac)|
|10010|ルーマニア語 (Mac)|
|10017|ウクライナ語 (Mac)|
|10021|タイ語 (Mac)|
|10029|中央ヨーロッパ言語 (Mac)|
|10079|アイスランド語 (Mac)|
|10081|トルコ語 (Mac)|
|10082|クロアチア語 (Mac)|
|20000|繁体字中国語 (CNS)|
|20001|TCA 台湾|
|20002|繁体字中国語 (Eten)|
|20003|IBM5550 台湾|
|20004|TeleText 台湾|
|20005|Wang 台湾|
|20105|西ヨーロッパ言語 (IA5)|
|20106|ドイツ語 (IA5)|
|20107|スウェーデン語 (IA5)|
|20108|ノルウェー語 (IA5)|
|20127|US-ASCII|
|20261|T.61|
|20269|ISO-6937|
|20273|IBM EBCDIC (ドイツ)|
|20277|IBM EBCDIC (デンマーク - ノルウェー)|
|20278|IBM EBCDIC (フィンランド - スウェーデン)|
|20280|IBM EBCDIC (イタリア)|
|20284|IBM EBCDIC (スペイン)|
|20285|IBM EBCDIC (英国)|
|20290|IBM EBCDIC (日本語カタカナ)|
|20297|IBM EBCDIC (フランス)|
|20420|IBM EBCDIC (アラビア語)|
|20423|IBM EBCDIC (ギリシャ語)|
|20424|IBM EBCDIC (ヘブライ語)|
|20833|IBM EBCDIC (韓国語拡張)|
|20838|IBM EBCDIC (タイ語)|
|20866|キリル言語 (KOI8-R)|
|20871|IBM EBCDIC (アイスランド語)|
|20880|IBM EBCDIC (キリル言語 - ロシア語)|
|20905|IBM EBCDIC (トルコ語)|
|20924|IBM ラテン-1|
|20932|日本語 (JIS 0208-1990 および 0212-1990)|
|20936|簡体字中国語 (GB2312-80)|
|20949|韓国語 Wansung|
|21025|IBM EBCDIC (キリル言語 セルビア - ブルガリア)|
|21027|拡張アルファベットの小文字|
|21866|キリル言語 (KOI8-U)|
|28591|西ヨーロッパ言語 (ISO)|
|28592|中央ヨーロッパ言語 (ISO)|
|28593|ラテン 3 (ISO)|
|28594|バルト言語 (ISO)|
|28595|キリル言語 (ISO)|
|28596|アラビア語 (ISO)|
|28597|ギリシャ語 (ISO)|
|28598|ヘブライ語 (ISO-Visual)|
|28599|トルコ語 (ISO)|
|28603|リトアニア語 (ISO)|
|28605|ラテン 9 (ISO)|
|29001|ヨーロッパ|
|38598|ヘブライ語 (ISO-Logical)|
|50000|ユーザー定義|
|50001|自動選択|
|50220|日本語 (JIS)|
|50221|日本語 (JIS 1 バイト カタカナ可)|
|50222|日本語 (JIS 1 バイト カタカナ可 - SO/SI)|
|50225|韓国語 (ISO)|
|50227|簡体字中国語 (ISO-2022)|
|50229|繁体字中国語 (ISO-2022)|
|50930|IBM EBCDIC (日本語および日本語カタカナ)|
|50931|IBM EBCDIC (日本語および米国 - カナダ)|
|50932|日本語 (自動選択)|
|50933|IBM EBCDIC (韓国語および韓国語拡張)|
|50935|IBM EBCDIC (簡体字中国語)|
|50936|簡体字中国語 (自動選択)|
|50937|IBM EBCDIC (繁体字中国語)|
|50939|IBM EBCDIC (日本語および日本語 - ラテン語)|
|50949|韓国語 (自動選択)|
|50950|繁体字中国語 (自動選択)|
|51251|キリル言語 (自動選択)|
|51253|ギリシャ語 (自動選択)|
|51256|アラビア語 (自動選択)|
|51932|日本語 (EUC)|
|51936|簡体字中国語 (EUC)|
|51949|韓国語 (EUC)|
|52936|簡体字中国語 (HZ)|
|54936|簡体字中国語 (GB18030)|
|57002|ISCII デバナガリ文字|
|57003|ISCII ベンガル語|
|57004|ISCII タミール語|
|57005|ISCII テルグ語|
|57006|ISCII アッサム語|
|57007|ISCII オリヤー語|
|57008|ISCII カンナダ語|
|57009|ISCII マラヤーラム語|
|57010|ISCII グジャラート語|
|57011|ISCII パンジャブ語|
|65000|Unicode (UTF-7)|
|65001|Unicode (UTF-8)|

</div></details>

# このソースコードのライセンス (MIT License or Public Domain)

Public Domain (Unlicense) に関しては https://ja.wikipedia.org/wiki/Unlicense もご覧ください。
> Unlicenseは、パブリックドメインに非常に近いライセンスであり、著作権法への抵抗に重点を置いている。2010年1月1日（パブリックドメインの日）に初めて提唱された。Unlicenseはパブリックドメインとして権利を放棄する手段を提供しており、帰属の必要がない非常に緩いライセンスともいえる。2015年にGitHubは、github.com上のライセンスされた全プロジェクト500万弱のうち、2パーセントに当たる約10万のプロジェクトがUnlicenseを採用していると発表した。

```
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2019-2021 JavaCommons
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----------------------------------------------------------------------------
```