このページはC++20に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
概要
UTF-8でエンコードされた文字を格納することを想定した型として、符号なし文字型char8_t
型を追加する。
char8_t
型はunsigned char
型と同じ大きさ、アライメント、整数変換順位であるが、独立した型となっており、char
やunsigned char
とはオーバーロードで区別される。
u8
プレフィックスの付いた文字/(生)文字列リテラルの型もchar
/const char [n]
からchar8_t
/const char8_t [n]
に変更になる。
<string>
ヘッダにはstd::basic_string<char8_t>
の別名であるstd::u8string
型が追加される。同様にして<string_view>
ヘッダにはstd::basic_string_view<char8_t>
の別名であるstd::u8string_view
型が追加される。
std::filesystem::path
クラスのコンストラクタにchar8_t
版のオーバーロードが追加され、代わりに必要なくなったstd::filesystem::u8path()
関数は非推奨となる。
または破壊的変更として、以下の関数は、戻り値としてchar
からchar8_t
の文字列を扱うよう変更される:
std::filesystem::path::u8string()
std::filesystem::path::generic_u8string()
std::basic_string
のリテラル演算子operator ""s
std::basic_string_view
のリテラル演算子operator ""sv
char
系の(ナローマルチバイト)文字列とchar8_t
系の(UTF-8)文字列の変換のために、<cuchar>
ヘッダにstd::mbrtoc8()
/std::c8rtomb()
関数が追加される。
ただし、basic_ostream<char>::operator<<()
とbasic_istream<char>::operator>>()
に対してchar8_t
のオーバーロードは追加されない。これは現状char16_t
/char32_t
型に対しても存在していないためである。正規表現も同様。
備考
機能テストマクロは以下の通り。
マクロ名 | 値 |
---|---|
__cpp_char8_t |
201811 |
__cpp_lib_char8_t |
201811 201907 (P1423R3によって更新) |
例
#include <iostream>
template<typename> struct ct;
template<> struct ct<char> {
using type = char;
};
int main()
{
const auto *u8s = u8"text"; // u8sの型はC++17まではconst char *だったが、C++20からはconst char8_t *になる
const char *ps = u8s; // C++17までは適格だったがC++20からは不適格
const auto *u8rs = u8R"(text)"; // u8rsの型はC++17まではconst char *だったが、C++20からはconst char8_t *になる
const char *prs = u8rs; // C++17までは適格だったがC++20からは不適格
auto u8c = u8'c'; // u8cの型はC++17まではcharだったが、C++20からはchar8_tになる
char *pc = &u8c; // C++17までは適格だったがC++20からは不適格
std::string s = u8"text"; // C++17までは適格だったがC++20からは不適格
void f(const char *s);
f(u8"text"); // C++17までは適格だったがC++20からは不適格
ct<decltype(u8'c')>::type x; // C++17までは適格だったがC++20からは不適格
}
#include <iostream>
template<typename> struct ct;
template<> struct ct<char> {
using type = char;
};
int main()
{
const auto *u8s = u8"text"; // u8sの型はC++17まではconst char *だったが、C++20からはconst char8_t *になる
const char *ps = u8s; // C++17までは適格だったがC++20からは不適格
const auto *u8rs = u8R"(text)"; // u8rsの型はC++17まではconst char *だったが、C++20からはconst char8_t *になる
const char *prs = u8rs; // C++17までは適格だったがC++20からは不適格
auto u8c = u8'c'; // u8cの型はC++17まではcharだったが、C++20からはchar8_tになる
char *pc = &u8c; // C++17までは適格だったがC++20からは不適格
出力
この機能が必要になった背景・経緯
C++の元になったC言語がISOで標準規格になる前から文字を格納する型としてchar
型ないしint
型が存在した。C++もこれを整理しつつ受け継いだ。
一方で8bitでは文字が収まらない文字エンコードも複数登場していた。日本語UNIX環境の開発から生まれたDEC漢字、その後Unixで普及したEUC、そしてUnicodeである。
C言語が初めて標準化された1989年、まだUnicodeはこんにちほど普及しておらず、どの文字エンコードが広く普及するのか、あるいは統一されることはないのか、見通すことはできない状況にあった。
結果としてANSI C89/ISO C90ではwchar_t
型を導入するものの、どのようなエンコードを扱うかは未規定とされた。C++98もこれを継承した。
2001年、Unicode側からutf16_t
型を追加する提案があった。UTF-16に絞っているのはメモリー効率が良いこと、すでに当時、WindowsやJava、データベースがUTF-16に対応しており、UTF-16を保証する型が必要とされたからであった。これは採用されなかった。
その後絵文字の普及なども後押ししてUnicodeが世界中に普及した。
C++11ではchar16_t
/char32_t
型が追加された。しかしこの時UTF-8を保証するchar8_t
型は提案されなかった。下に示す江添亮氏の解説によればUTF-8はchar
型に格納すればよろしい、という考えによるものだ。
本の虫: C++標準化委員会の文書: P0370R1-P0379R0
C++11のときにchar8_tが必要だと訴えたら、charは古典的にバイト列を表現する型なので十分だ。char型以外の型があるのは混乱する。などと理解のないUnicodeの世界に生きていない名だたる委員達から散々に批判された。
2017年11月にW3Techsによって行われた調査によれば90%を超えるWebサイトの文字エンコードにUTF-8が用いられるようになった。
一方でC++でUTF-8を扱うには問題があった。UTF-8のcode unitの値域は128 (0x80)から255 (0xFF)の範囲 (8ビット目) にも及んでいる一方で、C++のchar
型は符号の有無が未規定である。そのため、次のコードは意図した挙動を示さない可能性がある。
#include <iostream>
bool is_utf8_multibyte_code_unit(char c) {
return c >= 0x80;
}
int main()
{
std::cout << std::boolalpha << is_utf8_multibyte_code_unit(u8"あ"[0]) << std::endl;// => trueにならない可能性がある
}
#include <iostream>
bool is_utf8_multibyte_code_unit(char c) {
return c >= 0x80;
}
int main()
{
std::cout << std::boolalpha << is_utf8_multibyte_code_unit(u8"あ"[0]) << std::endl;// => trueにならない可能性がある
}
この問題を回避するため、UTF-8の8ビット目の範囲を扱う必要がある場合は、static_cast
で符号なし文字型に変換して扱わなければならなかった。
#include <iostream>
bool is_utf8_multibyte_code_unit(char c) {
return static_cast<unsigned char>(c) >= 0x80;
}
int main()
{
std::cout << std::boolalpha << is_utf8_multibyte_code_unit(u8"あ"[0]) << std::endl;// => true
}
#include <iostream>
bool is_utf8_multibyte_code_unit(char c) {
return static_cast<unsigned char>(c) >= 0x80;
}
int main()
{
std::cout << std::boolalpha << is_utf8_multibyte_code_unit(u8"あ"[0]) << std::endl;// => true
}
またC++11で文字列リテラルに対して、C++17で文字リテラルに対してu8
プレフィックスが使えるようになり、これはUTF-8でエンコードされることを保証したが、その文字型としては依然としてchar
型が使われた。char
型ではどのようなエンコードの文字が格納されているか型レベルで判断できず、例としてC++17で追加されたファイルシステムライブラリのpath
クラスでは、UTF-8でエンコードされたパス文字列を受け取るためにコンストラクタと代入演算子でオーバーロードができず、u8path()
という関数を追加せざるをえなかった。
UTF-8の利用が広く利用されていく中で、C++でもUTF-8を扱う上で障害となる仕様を改める必要があった。そのためにchar8_t
型が必要となった。
検討されたほかの選択肢
提案文書N3398では以下のようにchar8_t
型をunsigned char
型の別名にすることが提案されていた。
typedef unsigned char char8_t;
以下のようにenum class
を利用する選択肢もあったが、提案文書P0372R0はchar8_t
型を使うためにヘッダのインクルードが必要になることは望ましくないと述べている。
enum class char8_t : unsigned char {};
関連項目
- char16_tとchar32_t
- UTF-8文字列リテラル
- UTF-8文字リテラル
u8path
path::u8string
path::generic_u8string
operator ""s
operator ""sv
operator<<
参照
char8_t
型を追加する提案文書
- N3398: String Interoperation Library
- P0372R0: A type for utf-8 data
- P0482R0: char8_t: A type for UTF-8 characters and strings
- P0482R1: char8_t: A type for UTF-8 characters and strings (Revision 1)
- P0482R2: char8_t: A type for UTF-8 characters and strings (Revision 2)
- P0482R3: char8_t: A type for UTF-8 characters and strings (Revision 3)
- P0482R4: char8_t: A type for UTF-8 characters and strings (Revision 4)
- P0482R5: char8_t: A type for UTF-8 characters and strings (Revision 5)
- P0482R6: char8_t: A type for UTF-8 characters and strings (Revision 6)