【问题标题】:What is the printf() formatting character for char8_t *?char8_t * 的 printf() 格式字符是什么?
【发布时间】:2020-03-11 16:52:08
【问题描述】:

2020 年 8 月 25 日更新

根据this,这个问题似乎有些无关紧要:

// GCC 10.2, clang 10.0.1  -std=c++20

int main(int argc, char ** argv) 
{
    char32_t single_glyph_32 = U'ア' ;
    char16_t single_glyph_16 = u'ア' ;
    // gcc:   error: character constant too long for its type
    // clang: error: character too large for enclosing character literal type
    char8_t single_glyph_8 = u8'ア' ;

    return 42;
}

char8_t 似乎只能处理一小部分 UTF-8 字形。因此,使用它或尝试打印它没有多大意义。

2019 年 11 月 15 日 14:04 提问

还有char8_t?

我假设某个地方有一些 C++20 决定,但我找不到它。 还有P1428,但该文档没有提及printf()family vs. char8_t *char8_t

使用std::cout 建议可能是一个答案。不幸的是,它不再编译了。

// does not compile under C++20
// error : overload resolution selected deleted operator '<<'
// see P1423, proposal 7
std::cout <<  u8"A2";
std::cout <<  char8_t ('A');

对于 C 2.x 和 char8_t

start from here

更新

我已经对 u8 序列中的单个元素进行了更多测试。 这确实行不通。 char8_t *printf("%s") 确实有效,但 char8_tprintf("%c") 是等待发生的意外。

请看 -- https://wandbox.org/permlink/6NQtkKeZ9JUFw4Sd -- 问题是,按照目前的现状,char8_t 没有实现,char8_t * 是。 -- 让我重复一遍:没有实现的类型来保存来自char8_t * 序列的单个元素。

如果您想要单个 u8 字形,则需要将其编码为 u8 字符串

char8_t const * single_glyph = u8"ア";

目前看来,打印上面的那种肯定的方法是

// works with warnings
std::printf("%s", single_glyph ) ;

要开始阅读这个主题,可能需要这两篇论文

  1. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2231.htm
  2. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1423r2.html

按这个顺序。


我的主要 DEVENV 是 VisualStudio 2019,带有 MSVC 和 CLANG 8.0.1,与 VS 一起提供。使用 std:c++latest。开发机为WIN10【版本10.0.18362.476】

【问题讨论】:

  • 我不希望 C++ 标准委员会将转换说明符添加到 printf,他们将把它留给 C 委员会。
  • C++终于获得了 UTF8 字符类型?哦,喜悦,眼泪......哦,亲爱的,想象一下所有假设char*和特定LANG的代码。
  • 如果您需要跨平台的 UTF-8 支持,使用第三方库。这是此时保持理智的唯一方法。
  • @n.'pronouns'm。 ...好吧,我想我已经过了不归路... :)
  • 开个玩笑,新引入的char8_t类型比char类型还差。

标签: c++ utf-8 c++20


【解决方案1】:

我是针对 C++ 的 char8_t P0482P1423 提案以及针对 C 的 N2231 提案(尚未被接受)的作者。

让我们想想下面的应该做什么:

printf("Hello %s\n", u8"Jöel");
std::cout << "Hello " << u8"Jöel" << "\n";

实际上,让我们后退一步。标准输出的接收端需要什么编码?有几种可能性。如果标准输出连接到控制台/终端,则预期的编码是控制台/终端配置的编码。在美国的 Windows 系统上,这很可能是CP437。在 UNIX/Linux 系统上,这可能是 UTF-8。在美国的 z/OS 系统上,这可能是 EBCDIC code page 037。如果标准输出已被重定向,那么预期的编码可能取决于语言环境。在美国的 Windows 系统上,这意味着活动代码页 (ACP),可能是 Windows 1252。在 UNIX/Linux 和 z/OS 上,它可能与控制台/终端相同(Windows 是这里的奇怪系统,其控制台编码与区域设置编码的默认值不同)。

回到那个示例代码。 UTF-8 编码的ö 字符(U+00F6,{LATIN SMALL LETTER O WITH DIAERESIS},编码为0xC30xB6)的预期或期望行为是什么?对于写入控制台的 Windows,要正确显示字符,需要将编码序列转码为 0x94,而对于需要与区域设置相关的输出的 Windows,则需要将其转码为 0xF6。对于 UNIX/Linux,可能应该通过该序列。对于 z/OS,可能需要转码为0xCC。但在所有这些系统上,这些默认值都是可配置的(例如,通过 LANG 环境变量)。

假设转码为运行时确定的编码是所需的行为,应如何处理转码错误?例如,如果目标编码缺少ö 的表示,应该怎么办?如果存在格式错误的 UTF-8 序列怎么办? printf应该停止并报告错误吗? std::cout 应该抛出异常吗?或者是否应该替换实现定义的字符,例如 U+FFFD {REPLACEMENT CHARACTER} 或 ?

如果std::cout 充满了std::codecvt 方面,会发生什么?据推测,该方面将期望传入的文本采用特定的编码。 UTF-8 文本在呈现给方面之前是否应该转码为执行字符集、区域设置相关编码或控制台/终端编码之一?如果有,是哪一个?实现是否必须知道流是否连接到控制台/终端?如果程序员想要覆盖默认值,例如总是写 UTF-8 怎么办?

这些是相当困难的问题,我们没有很好的答案。有人建议 std::u8out 作为一种明确选择加入 UTF-8 的方法,但不能解决预期的标准输出编码问题、codecvt 方面的问题以及其他 iostreams 问题,如隐式依赖于语言环境的格式。

就个人而言,为了在未来提供良好的 Unicode 支持,我认为我们将不得不投资于 iostreams 的替代品,它 1) 提供字节输出和分层的文本支持,2) 编码感知(在文本层),3) 独立于语言环境(但显式选择支持与语言环境相关的格式,如std::format 提供的格式),4) 比 iostream 性能更高。

SG16 想听听您的想法和建议。联系方式见https://github.com/sg16-unicode/sg16

【讨论】:

  • 我忘了说。我们解决上述限制的短期计划 (C++23) 是提供显式编码、解码和转码接口,如P1629 中所述。这将允许程序员根据需要在各种执行和 UTF 编码之间手动转码。
  • 亲爱的汤姆,我知道 P1629。这是好的和合乎逻辑的。但。我需要的“唯一的事情”是拥有printf(),完全实现并能够输出u8序列和单个元素。那是 char8_t *char8_t.-- 自 2011 年以来,u8 是“in”。而 char8_t 是 C++20 关键字。尽管如此,似乎没有必要的决定和实施。我可能认为 utf-8 现在是相当关键的任务。我认为整个 C++ 社区不能等到 2023 年才能完全确定 utf-8 并在标准 C++ 中实现。
  • 我同意 UTF-8 现在是相当关键的任务,尽管我希望在 C++20 中提供更多支持,但这不再是一种选择。我在 2016 年 11 月首次向委员会提交了char8_t,直到 2018 年 11 月才被接受。然后,今年又召开了几次会议,使 P1423 通过委员会。变化并不总是像我们希望的那样迅速发生。
  • 在我提供的答案中,我问printf("%&lt;something&gt;", u8"text") 的行为应该是什么。我不清楚。我怀疑您可能对应该发生的事情有意见,并且我进一步怀疑我们可以做出的设计决策会让您感到反感或有问题。您更喜欢哪种行为,为什么?
  • C++ 对于printf 和朋友的规范遵从C。 C++ 标准可以对这些函数提出额外的要求,但大多数 C++ 实现都遵从不受它们控制的 C 标准库的实现。对printf 进行更改实际上需要我们通过WG14。 WG14 至少三年内不会有新标准。所以,我们还需要一段时间才能看到printf 的变化;假设我们可以就这些更改应该是什么达成一致。
【解决方案2】:

char8_t * 的 printf() 格式化字符是什么?

没有将char8_t* 打印为字符串的格式说明符。由于类型不匹配,使用%s 在技术上是一种未定义的行为,并且clang 会警告您(https://godbolt.org/z/xcs9Wj):

printf("%s", u8"Привет, мир!");
...: warning: format specifies type 'char *' but the argument has type 'const char8_t *' [-Wformat]
  printf("%s", u8"Привет, мир!");
          ~~   ^~~~~~~~~~~~~~~~
          %s

所以你唯一能做的就是用%p打印这样的字符串作为指针,这不是很有用。

iostreams 也不适用于char8_t 字符串。例如,这不能在 C++20 中编译:

std::cout << u8"Привет, мир!";

在大多数平台上,普通的char 字符串已经是 UTF-8,在带有 MSVC 的 Windows 上,您可以使用 /utf-8 进行编译,这将为您提供主要操作系统上的 Unicode 支持。

对于可移植的 Unicode 输出,您可以使用 {fmt} 库,例如 (https://godbolt.org/z/3ejsaG):

#include <fmt/core.h>

int main() {
  fmt::print("Привет, мир!");
}

打印:

Привет, мир!

免责声明:我是 {fmt} 的作者。

【讨论】:

  • 确实如此。另外(我假设和你一样)我正在关注将&lt;cuchar&gt; 添加到符合 C++20 的编译器中。 AFAIK 这将是转换到/从char8_t * 的唯一标准方法。
  • 不幸的是,mbrtoc8 和 c8rtomb 使用有限,因为它们依赖于全局语言环境编码。
  • @ChefGladiator,你可能有兴趣关注WG14 N2620的进展。本文提出了 C 的新接口,以支持所有窄、宽和 UTF 编码之间的转换。
  • 非常感谢汤姆。虽然我们昨天需要解决方案而不是在 23 日。
  • @ChefGladiator,我花费的时间远远超过了应有的时间,但我终于向 WG14 提交了N2653 (char8_t: A type for UTF-8 characters and strings (Revision 1)),并向 gcc、libstdc++ 和 glibc 提交了一个实现。提交给 gcc here、libstdc++ here 和 glibc here 的补丁。
【解决方案3】:

printf 不是 C++20 本身定义的; C++20 通过引用包含了 C 标准库。它可能会引用 C18,但这与 C11 基本相同(没有新功能;只是修复了缺陷报告)。

【讨论】:

  • cout &lt;&lt; u8"A2"; cout &lt;&lt; char8_t ('A'); 不能在 C++20 中编译,根据 P1428,提案 7。
  • @ChefGladiator 我希望你没有投反对票。 printf一直是一个 C 函数。在 C++ 中,应该使用 cout、wcout 等流。Effective C++ 几乎在 20 年前就包含了该建议。如果std::cout 不起作用,您必须找到正确的流。使用 printf 仍然是一个 C 函数
  • @ChefGladiator:公平点,这甚至不是问题的一部分,所以我删除了它。
  • 对不起 MSalters :) @PanagiotisKanavos 请做更多的研究。或者也许不是。它会让你非常不开心。
  • @PanagiotisKanavos “在 C++ 中应该使用像 cout、wcout 这样的流”。直到它们被弃用,这可能很快就会发生。它们基本上被破坏了。 20 年前似乎是个好主意,现在突然变得笨拙和嘎嘎作响。 C++20 中已经有了更好的格式化机制(std::format)。
【解决方案4】:

使用 std::cout 建议可能是一个答案。不幸的是,它不再编译了。

对我来说,它编译得很好(我在 Wandbox 上的实验性 GCC 10.0.0 上进行了测试),但没有打印出您可能期望/想要的内容。


我读过这个SO answer,它指出char8_t 的实现方式与unsigned char 相同,尽管它们不是相同的类型(这不是@ 的typedef 987654329@)。

知道了这一点,你就可以写出类似这样的重载:

#include <iostream>

std::ostream & operator<<(std::ostream & os, const char8_t & c8)
{
    return os << static_cast<unsigned char>(c8);
}

那么你应该能够写出类似的东西:

char8_t a = 'u';
std::cout << a << std::endl;

它会输出:

而不是

117

我做了test here

我认为您应该能够为char8_t * 做一些等效的事情(编辑: example here)。


如果我没有抓住你的意思,请告诉我。

【讨论】:

  • 如果您在 Google 上搜索 char8_t、streams、cout 等,您会发现 C++ 20 委员会尚未决定如何处理输出,并且可能没有 想为这个版本做出决定。 SO中也有类似(可能重复)的问题。不同的操作系统处理输出的方式也不同。例如,Windows 使用 UTF16,即使 旧控制台 将文本转换为用户的区域设置。 Linux不使用UTF16,那么C++应该怎么做呢?
  • 事实上,您链接到的问题中的正确 答案是other 答案。这是由提案的作者提供的。
  • @PanagiotisKanavos 两者都是正确的。该提案的作者刚刚添加了有关别名规则的缺失信息。但是在我的 sn-p 中,我没有违反它,因为我不使用 char8_t* 来别名其他东西。由于实现与unsigned char 的实现类似,我没有看到任何阻止我将char8_t 转换为unsigned char 的东西,同样将char8_t* 转换为unsigned char *。大小、对齐方式……都是一样的,这样我就没有违反严格的别名规则。
  • 其他讨论表明不再允许这样做,例如this one - 运算符已被删除,我们现在该怎么办? Tom Honermann 再次回答,答案是 we don't yet have consensus for what the behavior of the deleted overloads should be。也许我们应该ping他
  • @Fareanor 请参阅 -- wandbox.org/permlink/6NQtkKeZ9JUFw4Sd -- 您的示例不适用于宽 utf8 字形。问题是,按照目前的现状,char8_t 没有实现,char8_t * 是。 -- 让我重复一遍:没有实现的类型来保存来自char8_t * 序列的单个元素。
猜你喜欢
  • 1970-01-01
  • 2013-06-22
  • 2013-06-22
  • 2018-11-22
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 2020-04-07
  • 2020-12-04
相关资源
最近更新 更多