Visual Studio C++20 日本語対応 (UTF-8導入) メモ


## 目標

本記事では Windows 用 Visual Studio における C++ プロジェクト開発において、
文字コードをデフォルトの CP932 (Shift-JIS 派生) から UTF-8 に変更するための手順をまとめる。
※文字数のカウントや文字列の分割など、文字列データの中身を参照する操作については記載していない。

C++20 で追加された `char8_t` を利用した、 UTF-8 固定のシステムの構築を目指す。

C++ 開発で文字コードが問題になるのは、大きく以下に分類される。
- ソースファイル自身の文字コード
- プログラムの入出力
- プログラム内の静的データ (文字列リテラル)

## 想定環境

- 検証時期: 2024 年 12 月
- Windows 11
- Visual Studio 2022 v17.12
- MSVC C++20 (執筆時点のバージョン)

特に C++20 で追加・変更された `char8_t` 型を使用する。

## ソースファイル

### C++ プロジェクトのプロパティに `/utf-8` オプションを追加

デフォルト(CP932)では UTF-8 BOM無しソースファイルを使用すると、C4819 警告が発生する。

```plaintext
warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいま す。データの損失を 防ぐために、ファイルを Unicode 形式で保存してください。
```

`/utf-8` オプションを追加することで、BOM無しの UTF-8 ソースファイルが正しく扱われるようになる。

1. **プロジェクトのプロパティ** を開く
2. 構成とプラットフォームを適宜選択
3. **構成プロパティ > C/C++ > コマンドライン > 追加のオプション** に次を追加:
   ```plaintext
   /utf-8
   ```

[https://learn.microsoft.com/ja-jp/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170](https://learn.microsoft.com/ja-jp/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170)

※厳密には `/utf-8` オプションではなく `/source-charset:utf-8` オプションのみを指定すれば十分。

#### (補足) その他の関連プロパティ

- **構成プロパティ > 詳細 > 文字セット** で `Unicode 文字セットを使用する` を選択
  - これは `_TCHAR` などの `tchar.h` 定義の仕組みを使用するための設定。この記事では UTF-8 を前提としているため、特に影響はない。
  - [[詳細] プロパティ ページ](https://learn.microsoft.com/ja-jp/cpp/build/reference/advanced-property-page?view=msvc-170)
  - [tchar.h での汎用テキスト マッピング](https://learn.microsoft.com/ja-jp/cpp/text/generic-text-mappings-in-tchar-h?view=msvc-170)

### `.editorconfig` で `charset` を指定

`.editorconfig` で `charset` を指定すると、VisualStudio上で新規作成されるソースファイルの文字コードが指定されたものになる。

1. Visual Studio ソリューションフォルダのルートに `.editorconfig` ファイルを作成し、以下を記述する。
  ```plaintext: .editorconfig
  root = true
  [*.{c,cc,cpp,cxx,h,hh,hpp,hxx}]
  charset = utf-8
  ```

[EditorConfig で一貫性のあるコーディング スタイルを定義する](https://learn.microsoft.com/ja-jp/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022)
[EditorConfig ドキュメント](https://editorconfig.org/)


## プログラム入出力、静的データ


### `char` の代わりに `char8_t` , `std::string` の代わりに `std::u8string` を使用する

`char`, `std::string` は文字コードについて何も保証しないため、代わりに `char8_t`, `std::u8string` を使用することで、UTF-8 文字列を扱うことをコード上で明確にする。
※前提として、プログラム間でやり取りされる文字列データに文字コードの区別はなく、単なるバイト列として扱われる。そのため、 `std::u8string` が UTF-8 文字列データとして有効であるかは別の問題。

[UTF-8エンコーディングされた文字の型として char8_t を追加 [P0482R6]](https://cpprefjp.github.io/lang/cpp20/char8_t.html)

### 文字リテラルのプレフィックスを `u8` にする

C++ コード内で文字リテラルを使用する場合は、 `u8` プレフィックスを付けることで UTF-8 文字コードにエンコードされる。

[UTF-8文字列リテラル [N2442]](https://cpprefjp.github.io/lang/cpp11/utf8_string_literals.html)


### Windows API で UTF-8 を扱う

例えば `fopen` に対する `_wfopen` など、Windows API ではワイド文字列 (Windows では UTF-16 を指す) を想定したC標準ライブラリ互換関数が用意されている。
こうしたワイド文字版API を呼び出す際には、UTF-8 文字列を UTF-16 に変換する必要がある。
※UTF-8 を直接渡す方法もある。: [Use UTF-8 code pages in Windows apps](https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)

ここでは Windows API の `MultiByteToWideChar` を使用した例を示すが、
[utfcpp](https://github.com/nemtrif/utfcpp) などのライブラリを使用した方が良い場面が多い。

```cpp: utf8_to_utf16.cpp
#include <string>
#include <windows.h>

// UTF-8 バイナリ (終端文字あり) を UTF-16 に変換する一例
// Windows API に渡すため u16string ではなく wstring を返す

std::wstring to_wstr(const std::u8string& u8str) {
  if (u8str.empty()) {
    return std::wstring();
  }
  // calculate the size of the destination buffer
  int wstr_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<const char*>(u8str.c_str()), static_cast<int>(u8str.size()), nullptr, 0);
  // if the input is invalid, MultiByteToWideChar returns 0
  if (wstr_size == 0) {
    throw std::runtime_error("Invalid UTF-8 sequence");
  }
  std::wstring wstr(wstr_size, L'\0');
  // convert UTF-8 to UTF-16
  int result = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<const char*>(u8str.c_str()), static_cast<int>(u8str.size()), wstr.data(), static_cast<int>(wstr.size()));
  // if the input is invalid, MultiByteToWideChar returns 0
  if (result == 0) {
    throw std::runtime_error("Invalid UTF-8 sequence");
  }
  return wstr;
}
```

[MultiByteToWideChar 関数 (stringapiset.h)](https://learn.microsoft.com/ja-jp/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar)
[Windows API の Unicode](https://learn.microsoft.com/ja-jp/windows/win32/intl/unicode-in-the-windows-api)


#### ANSI版 Windows API で UTF-8 を扱う 

`fopen` など C標準ライブラリ互換の関数は、内部でANSI版 Windows API を使用している。
プログラム自体のコードページを変更することで、ANSI版 Windows API に UTF-8 文字列を渡すことができる。

T.B.D.

[fopen, _wfopen](https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170)
[Use UTF-8 code pages in Windows apps](https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)
[Windows/VC++ で char を UTF8 で扱う](https://zenn.dev/tenka/articles/vc_utf8_prog2022_1)

### ファイルパスは `std::filesystem::path` を使用する。

ファイルパスに関しては `std::filesystem::path` で、文字コードに依存せず文字列を扱うことができる。
`path::native`, `path::u8string` などのメンバ関数で、文字コードを変換することも可能。

### コンソールのコードページを UTF-8 に変更する

プログラムをコンソールから呼び出す場合は、そのコンソール側のコードページ（使用する文字コード）を UTF-8 に変更する必要がある。
※コンソールを共有する他プロセスに影響を与える可能性があるため、注意が必要。

```cpp
#include <windows.h>

int main() {
    UINT in_codepage = GetConsoleCP();
    UINT out_codepage = GetConsoleOutputCP();

    // set codepage to UTF-8
    SetConsoleCP(CP_UTF8);
    SetConsoleOutputCP(CP_UTF8);

    // do something

    // restore original codepage
    SetConsoleCP(in_codepage);
    SetConsoleOutputCP(out_codepage);
    return 0;
}
```

[コンソールのコード ページ](https://learn.microsoft.com/ja-jp/windows/console/console-code-pages)

## 関連記事

この記事の内容は、以下の関連記事と一部内容が重複する。

- [Stack Overflow: Unicode 文字セットでも Shift-JIS で解釈される](https://ja.stackoverflow.com/questions/95259/unicode%E6%96%87%E5%AD%97%E3%82%BB%E3%83%83%E3%83%88%E3%81%A7%E3%82%82shift-jis%E3%81%A7%E8%A7%A3%E9%87%88%E3%81%95%E3%82%8C%E3%82%8B)
- [C++(Visual Studio)で UTF-8 を扱うための試行錯誤のメモ](https://qiita.com/BlueSilverCat/items/1e5af7fdaeaf8fd2289d)
- [Windows/VC++ で char を UTF8 で扱う](https://zenn.dev/tenka/articles/vc_utf8_prog2022_1)

## 参考文献

- [プログラマのための文字コード技術入門](https://amzn.asia/d/9DnwxZv)

## その他未整理

- > NTFS では、ファイル名が Unicode に格納されます。 これに対し、古い FAT12、FAT16、FAT32 ファイル システムでは、OEM 文字セットが使用されます。 
  - [ファイル名で使用される文字セット](https://learn.microsoft.com/ja-jp/windows/win32/intl/character-sets-used-in-file-names)
- [Unicode の文字数を書記素単位で数える](https://scrapbox.io/bbr-program-memo/Unicode%E3%81%AE%E6%96%87%E5%AD%97%E6%95%B0%E3%82%92%E6%9B%B8%E8%A8%98%E7%B4%A0%E5%8D%98%E4%BD%8D%E3%81%A7%E6%95%B0%E3%81%88%E3%82%8B)
- [C++コンソール出力にchar8_t文字列を出力したい！](https://onihusube.hatenablog.com/entry/2020/04/03/211442)