🕌

UTF-8文字列リテラルをC++20でも(無理矢理)使う

2023/03/25に公開

概要

reinterpret_castを使ってconst char*として扱う、というやり方が手っ取り早い.
好みによっては、UDLを使う方法もいいかもしれない.

困りごと

"cpp-yaml":https://github.com/jbeder/yaml-cpp が文字列をUTF-8として扱ってくれるのでUTF-8文字列リテラル(u8プレフィックス)を使っていたらC++20にしたときにコンパイラがお怒りになってしまいました. (msvc)

const YAML::Node config = YAML::LoadFile("config.yaml");
config[u8"日本語を使いたいときもあるよ"].as<int>(); // C++20 char8_t がある環境だとNG

// ちなみに、こんなことするとアクセスできない(キーを見つけられない)
config["日本語を使いたいときもあるよ"].as<int>(); // 当たり前だけど、cp932はUTF-8じゃないよ

場当たり的な対処

幸いcharへの読み換え(エイリアス)なのでreinterpret_castでキャストしてしまえば、期待通り動作します.

config[reinterpret_cast<const char *>(u8"日本語を使いたいときもあるよ")].as<int>();

reinterpret_cast使い散らすのも気が進まないので、何か良い方法が無いか探してみることに.

P1423R3

char8_tの下位互換性について書かれた"P1423R3":https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1423r3.html と、文章中にあるコードが"GitHub":https://github.com/tahonermann/char8_t-remediation にありました.

P1423R3 では、u8プレフィックスがchar8_tを返すようになるとコードがどのように壊れるかということと、その問題へのアプローチについて書かれています. 書かれているアプローチのうち検討してみたものは以下のものです.

char8_tを無効化する

コンパイルオプションで無効化してしまう方法. UTF-8しか無い、という環境での開発ならいいかもしれない... けど、そんな環境にいないのと、コードを長期的にメンテするにあたって首を閉めそうなので見送り.

オーバーロードの追加

自分たちで書いている部分に関しては、これで良さそう. ただ、今回は外部のライブラリを使うときに必要なので見送り.

UDLでcharにしてしまう(そしてマクロで包む)

UTF-8な文字列リテラルに対するUDLを実装する方法.

// UDLの実装は、以下を参照してください.
// https://github.com/tahonermann/char8_t-remediation/blob/master/char8_t-remediation.h

config[u8"日本語を使いたいときもあるよ"_as_char].as<int>(); 

// マクロを使うとこんな感じ
#if defined(__cpp_char8_t)
    #define U8(x) u8##x##_as_char
#else
    #define U8(x) u8##x
config[U8"日本語を使いたいときもあるよ"_as_char].as<int>(); 

いい感じ.

結局

githubにあるコードを移植して手元の環境で動くことまで確認したものの、reinterpret_cast+マクロで対応することにしました.
(UDLがnamespaceくっつけたまんま使えたら良かったんだけど)

Discussion