带有 u8、char8_t 和 std::string 的 C++20

C++20 with u8, char8_t and std::string

C++11 为我们带来了 UTF-8 文字的 u8 前缀,几年前我觉得这很酷,并在我的代码中加入了这样的东西:

std::string myString = u8"●";

这一切都很好,但问题出现在 C++20 中,它似乎不再编译,因为 u8 创建了一个 char8_t* 而这与 std::string 不兼容它只使用 char.

我应该创建一个新的 utf8string 吗?在 C++20 世界中,我们有更多与标准不匹配的显式类型 std::string?

Should I be creating a new utf8string?

不,它已经存在了。 P0482 不仅提出了 char8_t,而且还为 char8_t 字符类型提出了 std::basic_string 的新特化,名为 std::u8string。所以这已经用来自主干的 clanglibc++ 编译:

const std::u8string str = u8"●";

std::stringu8 文字中断的事实令人遗憾。来自提案:

This proposal does not specify any backward compatibility features other than to retain interfaces that it deprecates. The author believes such features are necessary, but that a single set of such features would unnecessarily compromise the goals of this proposal. Rather, the expectation is that implementations will provide options to enable more fine grained compatibility features.

但我想大多数像上面这样的初始化应该 grep-able 或受一些自动 clang 工具修复。

除了@lubgr 的回答之外,论文char8_t backward compatibility remediation (P1423) 还讨论了如何使用char8_t 字符数组制作std::string 的几种方法。

基本上,您可以将 u8 字符数组转换为 "normal" 字符数组以获得与 C++17 及之前版本相同的行为,您只需要稍微更明确。本文讨论了执行此操作的各种方法。

适合您的用例的最简单(但不是完全零开销,除非您添加更多重载)方法可能是最后一种,即引入显式转换函数:

std::string from_u8string(const std::string &s) {
  return s;
}
std::string from_u8string(std::string &&s) {
  return std::move(s);
}
#if defined(__cpp_lib_char8_t)
std::string from_u8string(const std::u8string &s) {
  return std::string(s.begin(), s.end());
}
#endif

Should I be creating a new utf8string?

不,C++20 添加了 std::u8string。但是,我建议改用 std::string,因为 char8_t 在标准中的支持很差,并且根本不受任何系统 API 的支持(并且可能永远不会因为兼容性原因)。在大多数平台上,正常的 char 字符串已经是 UTF-8,而在 Windows 和 MSVC 上,您可以使用 /utf-8 进行编译,这将为您在主要操作系统上提供可移植的 Unicode 支持。

例如,您甚至不能在 C++20 中使用 u8 字符串编写 Hello World 程序 (https://godbolt.org/z/E6rvj5):

std::cout << u8"Hello, world!\n"; // won't compile in C++20

在 Windows 上,对于 MSVC 和 C++20 之前的版本,情况更糟,因为 u8 字符串可能会悄无声息地损坏。例如:

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

将生成有效的 UTF-8,它可能会或可能不会显示在控制台中,具体取决于其当前代码页而

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

几乎肯定会给你一个无效的结果,例如 ╨а╤Я╨б╨В╨а╤С╨а╨Ж╨а┬╡╨бтАЪ, ╨а╤Ш╨а╤С╨б╨В!

可能不方便,但是你用这个: (const char*)u8"こんにちは"

或者使用参数“const char*”和“const char8_t*”创建 2 个函数

目前看来 utf8 到处都是提倡者,C++20 提供了还有另一个在决定如何处理字符编码时要考虑的有缺陷的不完整选项用于可移植代码。 char8_t 使一些已经很脏的水变得更加浑浊。作为 msvc optionPreview 的权宜之计,我能想到的最好的 - 来自最新 C++ 工作草案 (/std:c++latest) 的功能是这个...

#if defined(__cpp_char8_t)
template<typename T>
const char* u8Cpp20(T&& t) noexcept 
{ 
#pragma warning (disable: 26490)
   return reinterpret_cast<const char*>(t);
#pragma warning (default: 26490)
}
   #define U8(x) u8Cpp20(u8##x)
#else
   #define U8(x) u8##x
#endif

丑陋、低效且烦人。但它允许在遗留 'utf8 everywhere' 代码中将所有 u8"" 替换为 U8""。我计划回避 char8_t 直到产品更加连贯和完整(或永远)。我们应该拭目以待,看看 C++20 最终会做出什么决定。目前 char8_t 是一个巨大的失望。

如果有人感兴趣,我已经在 github(针对 visual studio 社区)上发布了我自己的 utf8 无处不在响应的开源示例。 https://github.com/JackHeeley/App3Dev

另一种将 u8 文字用作 const char* 的方法是用户定义的文字(参见 https://en.cppreference.com/w/cpp/language/user_literal):

std::string operator"" S(const char8_t* str, std::size_t) {
    return reinterpret_cast< const char* >(str);
}
char const* operator"" C(const char8_t* str, std::size_t) {
    return reinterpret_cast< const char* >(str);
}

用法: 然后可以这样使用:

std::string myString = u8"●"S;


SetConsoleOutputCP(CP_UTF8);
std::cout << u8"Привет, мир!"C << std::endl;

说明

上面的代码定义了两个用户定义的字面量u8"…"Su8"…"C(记住:C++20中的字面量u8"…"const char8_t*类型)。 S 文字创建了一个 std::stringC 文字创建了一个 const char *.

这意味着 u8"…"C 形式的所有文字都可以像 "…" 文字一样使用,而 u8"…"S 形式的所有文字都可以像 "…"s 文字一样使用。

PS:我不确定是否允许定义不以下划线“_”开头的文字。但是当我在 Visual Studio 中尝试时,代码 运行 没有问题。但是 cppreference 中的所有示例都带有下划线。