WindowsでもC++でUTF-8で読み書きしたい、他OSと同じビルドシステムを使いたい

# 対象読者

- macOSやLinuxの作法 (BOMなしUTF-8・char型もUTF-8・Makefileやそれを出力できるビルドシステム使用) でC++を書いている・書きたい人
- Cygwin・MinGW・MSYS・WSLのGCCでなく、MSVCでC++プログラムをコンパイルしたい人
- C++でクロスプラットフォーム開発したい人

# Windows(MSVC)でもそれ以外のOSでもビルドできるようにする

**特段の理由がなければCMakeを使いましょう。**最近はVisual Studioの対応も良好です。

ソースコードでUTF-8を使う場合、以下の2行を加えましょう。

```cmake
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
```

この`/utf-8`オプションは、コードのプリプロセッシング・コンパイルを行う際に、入力をUTF-8とみなすという意味です。ないと殆どの日本語環境でShift-JISとみなされます。また、[`.editorconfig`というファイルをルートに作成し、適切に設定する](https://editorconfig.org/)文字コード・改行コード・インデントスタイル等をエディタによらず統一できます。例を以下に示します。

```ini:.editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
tab_width = 8

[*.{py,rs}]
indent_size = 4

[{Makefile,*.{go,mk}}]
indent_style = tab

[*.md]
trim_trailing_whitespace = false

[*.{manifest,vcxproj,sln}]
charset = utf-8-bom
```

`CMakeLists.txt`には、

```cmake
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>")
```

の行も加えるとよいです。これは、C++11以降を利用した場合にマクロ変数`__cplusplus`の値を利用中のC++のバージョンに合わせるという意味合いです。つけないと、C++98相当の値となります。(一応MSVC独自のマクロ変数`_MSVC_LANG`で有効化しているC++のバージョンの判定は可能です、`__cplusplus`での判定と併用しましょう)
さらに、VS2019のMSVCはデフォルトでC++14なので、次の行も足してC++17に対応させるとよいでしょう。

```cmake
set(CMAKE_CXX_STANDARD 17)
```

以上を反映させた`CMakeLists.txt`のサンプルを以下に掲載します。`project`・`add_executable`の`()`内は適宜変更してください。また、`cmake_minimum_required`の値がやや厳しく、Ubuntu 18.04やVisual Studio 2017が弾かれてしまいますが、このバージョンからデフォルトで`add_executable`に渡すソースファイルのパスとして相対パスが使えるというのが理由です。`foobar.exe.manifest`に関しては後述します。

```cmake
cmake_minimum_required(VERSION 3.13)
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(foobar CXX)

add_executable(foobar foobar.cpp foobar.exe.manifest)
```

CMake以外の他の手段としてはqmake・Meson・Blazeあたりでしょうか。Qtを使うならqmakeもなくはないです。(一応CMakeでもQtのコンパイルは可能ですが、QtStudioからの新規作成ができなかったような気がする)

# Windowsでもワイド文字を使わずにUTF-8を扱う

戦略は2つあります。

1. Windowsに限り、UTF-8の入出力をUTF-16のワイド文字の間で変換する
2. 通常文字の入出力をShift-JISからUTF-8にする

## Windowsに限り、UTF-8の入出力をUTF-16のワイド文字の間で変換する

互換性を重視するならば、この方法がよいでしょう。[Boost.Nowide](https://github.com/boostorg/nowide)という非常に便利なライブラリがあります。これは、Shift-JISのプログラム引数(`argv`)や、他OSと非互換のワイド文字版のコンソール・ファイルI/OをUTF-8版のもので隠してくれます。
次のようなコードで、UTF-8対応は完了します。

```cpp:Boost.Nowideスタンドアロン版
#include <nowide/args.hpp>
#include <nowide/iostream.hpp>
//#include <nowide/fstream.hpp> // ファイルの入出力をする場合

int main(int argc, char **argv) {
  nowide::args _(argc, argv);  // インスタンス「_」が生存している間argvはUTF-8版のものを指す
  nowide::cout << "日本語だよ\n"; // nowide::coutの中で「std::wcout << L"日本語だよ\n"」相当の処理がされる
  for(int i = 0; i < argc; ++i) {
    nowide::cout << "引数" << i << ": " << argv[i] << '\n';
  }
  return 0; // main終了直前、「_」が破棄され、argvは元のShift-JIS版に戻る
}  
```

問題はライブラリのビルド・リンクですが、[Vcpkg](https://github.com/microsoft/vcpkg)というC++ライブラリ用のパッケージマネージャで一発インストール可能です。公式ページの手順でインストールしたら、次のコマンドを実行してインストールします。

```pwsh
.\vcpkg install nowide:x64-windows-static
```

その後、`CMakeLists.txt`に次の2行を足します。`[foobar]`を実行ファイル名にリネームすることをお忘れなく。2行の間に`add_executable`が入るような形がよいかもしれません。

```cmake
find_package(nowide CONFIG REQUIRED)
target_link_libraries([foobar] PRIVATE nowide::nowide)
```

ビルドは次のように行います。

```pwsh
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=[Vcpkgのルート]\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build .
```


## 通常文字の入出力をShift-JISからUTF-8にする

おそらくWindows10専用の手法だと思いますが、今やWindows7・8は空気になりつつあるので無視します。

### 引数を使う場合

`setlocale`・`std::locale::global`などに空文字のデフォルトロケールを渡します。

```c:C兼用
#include <locale.h>
#include <stdio.h>

int main(int argc, char **argv) {
  setlocale(LC_ALL, "");
  puts("日本語だよ");
  for(int i = 0; i < argc; ++i) {
    printf("引数%d: %s\n", i, argv[i]);
  }
  return 0;
}
```

```c:C++専用
#include <locale>
#include <iostream>

int main() {
  std::locale::global(std::locale(""));
  std::cout << "日本語だよ\n";
  for(int i = 0; i < argc; ++i) {
    std::cout << "引数" << i << ": " << argv[i] << '\n';
  }
  return 0;
}
```

そして、以下のマニフェストファイル

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <application>
    <windowsSettings>
      <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
    </windowsSettings>
  </application>
</assembly>
```

を埋め込みます。CMakeを利用する場合、ソースファイルと一緒に`add_executable`に渡すだけでOKです。
このページを見なくても、[自作のコマンドラインツール](https://github.com/tats-u/forceu8exe)でマニフェストファイルをファイルに出力することができます。~~急いで作ったのでソース汚い、コマンド名微妙と散々ですが~~
Rustのインストールが済んでいれば以下の2行で生成完了です。~~Cargoという配布手段がなかったらRustなんて使っていない~~

```pwsh
cargo install --git https://github.com/tats-u/forceu8exe.git
forceu8exe manifest .\foobar.exe.manifest
```

### 引数は無視する場合(非推奨)

以下の`main()`直後の文を丸パクすればOKです。マニフェストは不要です。このプログラムをコンパイルすれば、`chcp 932`なWindows(MSVC)でも、Linuxでも、文字化けせず「日本語だよ」と表示されます。

```c:C兼用
#include <locale.h>
#include <stdio.h>

int main(void) {
  setlocale(LC_ALL,
#if _WIN32 && !__MINGW32__ && !__CYGWIN__
    ".UTF-8"
#else
    ""
#endif
    );
  puts("日本語だよ");
  return 0;
}
```

```c++:C++
#include <locale>
#include <iostream>

int main() {
  std::locale::global(std::locale(
#if _WIN32 && !__MINGW32__ && !__CYGWIN__
    ".UTF-8"
#else
    ""
#endif
    ));
  std::cout << "日本語だよ\n";
  return 0;
}
```

## WindowsとUnix系OSとのロケール・文字種の違い

Unix系では、`setlocale`で文字コードを指定すると、ワイド文字(`wchar_t`)の文字列をどのエンコードに従ってマルチバイト文字列(`char`)に変換するかが変わります。`setlocale`はしてもしなくても、またどのような値を指定しても、マルチバイト文字列はそのまま(ソースコードに書いたまま)出力されます。また、現在ではマルチバイト文字のエンコーディングはほぼUTF-8固定なので、マルチバイト文字でも問題が起こらないと言えます。
Windowsでは逆です。Windows(NT・2000以降)はワイド文字がメインかつ内部処理はワイド文字(UTF-16)固定で、マルチバイト文字APIは環境によってエンコーディングが変わります。Windows10限定の特殊な設定をしない限り、例えば日本語圏ではShift-JIS、英語圏ではLatin-1です。なので、マルチバイト文字をうかつに扱えません。UTF-8が使えるようになったのはWindows10になってからです。無理やり1つのエンコーディングに固定することもできますが、Windows8.1以前か、そのエンコーディングが表す言語以外か、どちらかを切り捨てざるを得ませんでした。MSもマルチバイト文字を推奨しています。
今までは、MSVCもGCCも両方使うC++erはこの2つの作法の板挟みになっていました。しかもWindowsとそれ以外ではワイド文字の長さも違います。(Windowsが2バイト、他は4バイト)絵文字などが混じったら目も当てられません。
大人しく環境によって2つの文字を使い分ける、Qtの文字クラスに頼る、あるいは日本語または絵文字(や𠮷など一部の漢字)の取り扱いを諦めるなど、あの手この手でごまかしてきました。

## Windows10でのUTF-8の扱いの改善

Windows10では、マルチバイト文字のエンコーディングをUTF-8にできるようになりました。コマンド`chcp 65001`によって開いているコンソールの入出力をUTF-8にするのが実用的になったのが最初で、後にOSデフォルトのエンコーディングもShift-JISではなくUTF-8にできるようになりました。
残る問題は個別ソフトの対応です。SJISの環境でもUTF-8の環境でもUTF-8で出力された日本語を表示できるようになればいいのですが、その方法は長らくマイナーでした。

## Microsoft Docs

- [Microsoft Docsによる`setlocale`の説明](https://docs.microsoft.com/ja-jp/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=vs-2019)
- [Microsoft DocsにおけるUTF-8を利用する方法の説明](https://docs.microsoft.com/ja-jp/windows/uwp/design/globalizing/use-utf8-code-page)