C++20中的'char8_t'和我们旧版本的'char'是相同的吗?

73
在CPP参考文档文档中,我注意到对于char,字符类型足够大以表示任何UTF-8八位代码单元(自C++14起),而对于char8_t,它是用于UTF-8字符表示的类型,要求足够大以表示任何UTF-8代码单元(8位)。这是否意味着两者是相同的类型?还是char8_t有其他特征?

11
从观察中可以明显看出char8_t是一个8位类型。此外,char的符号取决于编译器和目标平台:ARM和PowerPC的默认值通常为无符号,而x86和x64的默认值通常为有符号。与此不同的是,char8_t始终为无符号类型。 - Elliott Frisch
1
从逻辑上讲,代码可以假定一个 char8_t 字符串始终包含 UTF-8 文本(除非存在错误),而在没有额外环境知识的情况下,假定 char 字符串的任何特定编码则不太安全。 - Miral
8
好的,有一些好处。char类型,像许多C++的C遗产一样,一直以来都非常令人讨厌和不靠谱。你不知道它是符号位还是非符号位,严格来说你甚至不知道它有多少位(尽管8位是一个相当安全的猜测,但绝无任何保证)。char8_t类型提供了这两个保证。不幸的是,没有人有勇气简单地“修复”已经破损的原始类型(虽然这可能会破坏现有代码,但现代C++与传统C++不兼容,所以有什么关系...)。就像没有人费心将size_tptrdiff_t变成一个“适当”的类型一样。 - Damon
5
根据这个评论,没有要求 char8_t 精确为八位,所以在这方面没有发生任何变化... - Holger
5
@Damon C一直保证char至少有8个位。POSIX和大多数其他系统(如Windows)保证char恰好是8位。但C并没有说“是的,什么什么POSIX”。 POSIX包含了C标准,而不是相反。除非C突然决定疏远其巨大的用户群,否则他们不会强制规定恰好有8个位的类型,因为C是用于编写所有现代嵌入式/特定领域硬件的主要语言,这些硬件的字节比8位大。 - mtraceur
显示剩余4条评论
2个回答

103
免责声明:我是P0482P1423提案的作者。
在C++20中,char8_t是不同于其他类型的独立类型。在相关的C提案N2653中,char8_tunsigned char的typedef,类似于现有的char16_tchar32_t的typedef。
在C++20中,char8_t有一个底层表示与unsigned char相匹配。因此,它具有与unsigned char相同的大小(至少为8位,但可能更大)、对齐方式和整数转换等级,但具有不同的别名规则。

特别地,[basic.lval]p11[basic.life]p6.4[basic.types]p2[basic.types]p4未将char8_t添加到类型列表中。这意味着,与unsigned char不同,它不能用于另一种类型对象的基础存储,也不能用于查看其他类型对象的底层表示方法;换句话说,它不能用于别名其他类型。这导致char8_t类型的对象可以通过指向charunsigned char的指针访问,但不能使用指向char8_t的指针来访问charunsigned char数据。换句话说:

reinterpret_cast<const char   *>(u8"text"); // Ok.
reinterpret_cast<const char8_t*>("text");   // Undefined behavior.

具有这些属性的独特类型的动机是:
  1. 为UTF-8字符数据提供一个不同的类型,与具有区域设置依赖性或需要单独指定编码的字符数据不同。

  2. 使普通字符串字面量与UTF-8字符串字面量(因为它们可能具有不同的编码)可以进行重载。

  3. 确保UTF-8数据的无符号类型(char是有符号还是无符号是实现定义的)。

  4. 通过非别名类型实现更好的性能;优化器可以更好地优化不与其他类型别名的类型。


16
为什么是char8_t而不是uchar8_t? - Mala
31
因为char8_tchar16_tchar32_t(也是无符号类型)保持一致。 - Tom Honermann
1
在列出的原因中,我个人认为只有2和4是有意义的。1可以使用常规char类型实现,而3则无关紧要,除非您对字符执行算术运算。 - Martin
1
@Martin,是的,普通的char可以用于操作UTF-8数据,但有充分的证据表明程序员在维护字符编码与存储在char中的数据之间的正确关联方面存在困难;乱码问题仍然存在。 char8_t当然不能解决所有这些问题,但它确实提供了一些保护措施。至于3,char中的位数以及它是有符号还是无符号都会影响检查前导和尾随代码单元。c >= 0x80不是检查尾随代码单元值的可移植方式。 - Tom Honermann
1
@Martin,虽然存在使用CHAR_BIT不等于8的实现,而标准必须对所有理论实现保持内部一致性,但这些实现并不是char8_t的动机。对于尾随代码单元检查示例,请注意对于具有8位有符号char类型的实现,“c >= 0x80”(其中c的类型为char)始终为false。对于这样的实现,可以使用“c < 0”,但对于具有无符号char类型的实现,这总是为false。 - Tom Honermann
显示剩余9条评论

58

char8_t不同于char。根据[basic.fundamental]/9,它的行为与unsigned char完全相同。

类型char8_­t表示一个独特的类型,其基础类型为unsigned char。类型char16_­tchar32_­t表示独特的类型,其基础类型分别为<cstdint>中的uint_­least16_­tuint_­least32_­t

强调是我的


请注意,由于标准将其称为独特的类型,因此像以下代码一样:

std::cout << std::is_same_v<unsigned char, char8_t>;

即使char8_t实现为unsigned char,打印出的结果仍为0(false),这是因为它不是一个别名而是一种不同的类型。
另一件需要注意的事情是,char可以被实现为signed charunsigned char。这意味着char可能具有与char8_t相同的范围和表示方式,但它们仍然是不同的类型。charsigned charunsigned charchar8_t大小相同,但它们都是不同的类型。

10
@MichaelDorgan 但是98比17大,而且98并不是那么好玩的工作 ;) - NathanOliver
11
如果你没有意识到的话,C语言还有char16_tchar32_t和相关的字符/字符串字面量和操作函数。(当然也有charunsigned charsigned charint8_tuint8_t - M.M
8
那么,我们实际上需要对已经存在的东西再起一个名字吗? - Michael Chourdakis
22
“那么,我们真的需要从已有的东西中再取一个名称吗?” 是的。如果我给你一个 const char*,它是 UTF-8 编码的吗?你不知道。如果我给你一个 const char8_t*,那么如果它 不是 UTF-8 编码,那么 我就是个骗子。类型很重要,如果 C++ 要获得良好的 Unicode 支持,我们必须拥有代表以 Unicode 编码的字符串的类型,而不仅仅是编译器想要的任何类型。char8_t 的唯一真正问题是很少有现有的 API 可以使用它们。随着 Unicode 的完成,这个问题将得到解决。 - Nicol Bolas
13
有趣的是,char8_t不一定要恰好是8位。因为它与unsigned char具有相同的表示形式,所以它是CHAR_BIT位。与uint8_t不同,如果没有8位整数类型,则其未定义,而char8_t始终被定义。(可能不存在CHAR_BIT != 8的主机实现。) - Keith Thompson
显示剩余19条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接