C++ 标准是否要求对 wchar_t 进行编码?

Does the C++ standard mandate an encoding for wchar_t?

以下是我的 2014 草案标准 N4140 的一些摘录

22.5 Standard code conversion facets [locale.stdcvt]

3 For each of the three code conversion facets codecvt_utf8, codecvt_utf16, and codecvt_utf8_utf16:
(3.1) — Elem is the wide-character type, such as wchar_t, char16_t, or char32_t.

4 For the facet codecvt_utf8:
(4.1) — The facet shall convert between UTF-8 multibyte sequences and UCS2 or UCS4 (depending on the size of Elem) within the program.

对这两段的一种解释是 wchar_t 必须编码为 UCS2 或 UCS4。我不太喜欢它,因为如果它是真的,我们就有一个重要的 属性 语言深埋在库描述中。我试图找到更直接的说法属性,但无济于事。

另一种解释是 wchar_t 编码不需要是 UCS2 或 UCS4,并且在它不是的实现中,codecvt_utf8 不适用于 wchar_t。我也不太喜欢这种解释,因为如果它是真的,并且 charwchar_t 原生编码都不是 Unicode,那么似乎没有办法在这些原生编码和Unicode.

这两种解释哪一种是正确的?还有一个我忽略了吗?

澄清 我不是在询问有关 wchar_t 对软件开发的适用性或 wchar_t 可以从其他地方获得的属性的一般意见。我对标准的这两个特定段落感兴趣。我试图了解这些特定段落 包含或不包含的内容。

澄清 2。如果4.1说"The facet shall convert between UTF-8 multibyte sequences and UCS2 or UCS4 or whatever encoding is imposed on wchar_t by the current global locale"就没有问题了。它没有。它说它说的。看起来,如果一个人使用 std::codecvt_utf8<wchar_t>,那么无论当前的全球语言环境如何,都会以一堆编码为 UCS2 或 UCS4 的 wchar_t 结束。 (无法为 codecvt_utf8 指定语言环境或任何字符转换方面)。所以这个问题可以这样改写:转换结果是否可以直接用于当前全局语言环境(and/or 和任何可能的语言环境)用于输出、wctype 查询等等?如果不是,它对 有什么用? (如果上面第二种解释正确,答案似乎是"nothing")。

没有

wchar 只需要保存编译器支持的最大语言环境。理论上可以放入一个字符中。

Type wchar_t is a distinct type whose values can represent distinct codes for all members of the largest extended character set specified among the supported locales (22.3.1).

— C++ [basic.fundamental] 3.9.1/5

因此它甚至不需要支持 Unicode

The width of wchar_t is compiler-specific and can be as small as 8 bits. Consequently, programs that need to be portable across any C or C++ compiler should not use wchar_t for storing Unicode text. The wchar_t type is intended for storing compiler-defined wide characters, which may be Unicode characters in some compilers.

ISO/IEC 10646:2003 Unicode standard 4.0

让我们区分 wchar_t 和使用 L 前缀构建的字符串文字。

wchar_t只是一个整型,可能比char.

使用 L 前缀的字符串文字将生成使用 wchar_t 个字符的字符串。这究竟意味着什么取决于实现。不要求此类文字使用 任何特定编码。他们可能使用 UTF-16、UTF-32 或其他与 Unicode 完全无关的东西。

因此,如果您希望 保证 的字符串文字在所有平台上以 Unicode 格式编码,请使用 u8u,或字符串文字的 U 前缀。

One interpretation of these two paragraphs is that wchar_t must be encoded as either UCS2 or UCS4.

不,这不是一个有效的解释。 wchar_t 没有编码;这只是一种类型。编码的是data。以 L 为前缀的字符串文字可能会或可能不会在 UCS2 或 UCS4 中编码。

如果您提供 codecvt_utf8 一串 wchar_t 编码为 UCS2 或 UCS4(适用于 sizeof(wchar_t)),那么它将起作用。但不是因为 wchar_t;它之所以有效,是因为您提供的 data 已正确编码。

If 4.1 said "The facet shall convert between UTF-8 multibyte sequences and UCS2 or UCS4 or whatever encoding is imposed on wchar_t by the current global locale" there would be no problem.

那些 codecvt_* 方面的全部要点是执行 与区域设置无关的 转换。如果你想要依赖语言环境的转换,你不应该使用它们。您应该改为使用全局 codecvt 方面。

Microsoft 似乎分享了您的第一个结论,他们列举了可能的选项,并注意 UTF-16,尽管 "widely used as such[sic]" 不是有效的编码。

QNX也使用了相同的措辞,指出了措辞的来源:QNX 和 Microsoft 都从 Dinkumware 派生了他们的标准库实现。

现在,碰巧的是,Dinkumware 也是介绍这些 类 的 N2401 的作者。所以我要站在他们一边。

由于 Elem 可以是 wchar_tchar16_tchar32_t,因此第 4.1 条未提及所需的 wchar_t 编码。它说明了有关执行的转换的一些信息。

从字面上可以看出,转换是在 UTF-8 和 UCS-2 或 UCS-4 之间进行的,具体取决于 Elem 的大小。因此,如果 wchar_t 是 16 位,则转换将使用 UCS-2,如果是 32 位,则转换为 UCS-4。

为什么标准提到 UCS-2 和 UCS-4 而不是 UTF-16 和 UTF-32?因为 codecvt_utf8 会将多字节 UTF8 转换为单个宽字符:

  • UCS-2是unicode的一个子集,但是有no surogate pair encoding与UTF-16相反
  • UCS-4 与 UTF-32 相同,现在(但是看着越来越多的表情符号,也许有一天 32 位不够,你会得到一个 UTF-64,并且codecvt_utf8)
  • 不支持的 UTF32 代理对

尽管我不清楚会发生什么,如果 UTF-8 文本包含的序列对应于 UCS-2 中不可用的 unicode 字符,用于接收 char16_t.

wchar_t 只是一个完整的文字。它有一个最小值,一个最大值等。

它的大小不是标准固定的。

如果足够大,可以将UCS-2或UCS-4数据存储在wchar_t的缓冲区中。无论您使用什么系统都是如此,因为 UCS-2 和 UCS-4 以及 UTF-16 和 UTF-32 只是按顺序排列的整数值的描述。

在 C++11 中,有 std API 可以读取或写入假定数据具有这些编码的数据。在 C++03 中,有使用当前语言环境读取或写入数据的 API。

22.5 Standard code conversion facets [locale.stdcvt]

3 For each of the three code conversion facets codecvt_utf8, codecvt_utf16, and codecvt_utf8_utf16:

(3.1) — Elem is the wide-character type, such as wchar_t, char16_t, or char32_t.

4 For the facet codecvt_utf8:

(4.1) — The facet shall convert between UTF-8 multibyte sequences and UCS2 or UCS4 (depending on the size of Elem) within the program.

所以这里 codecvt_utf8_utf16 一方面处理 utf8,另一方面处理 UCS2 或 UCS4(取决于 Elem 的大小)。它进行转换。

Elem(宽字符)根据其大小假定为 UCS2 或 UCS4 编码。

这并不意味着 wchar_t 是这样编码的,它只是意味着 此操作将 wchar_t 解释为这样编码

UCS2 或 UCS4 如何进入 Elem 不是标准的这一部分关心的内容。也许你用十六进制常量将它设置在那里。也许你是从 io.也许你是即时计算的。也许您使用了高质量的随机数生成器。也许您将 ascii 字符串的位值加在一起。也许您计算了月球将地球日改变 1 秒所需秒数的 log* 的定点近似值。 不是这些段落的问题。这些段落只是规定如何修改和解释位。

类似的说法在其他情况下也成立。这并不强制要求 wchar_t 具有什么格式。它只是说明了这些方面如何解释 wchar_tchar16_tchar32_tchar8_t(读或写)。

wchar_t 交互的其他方式使用不同的方法来规定如何解释 wchar_t 的值。

例如,

iswalpha 使用(全局)语言环境来解释 wchar_t。在某些当地人中,wchar_t 可能是 UCS2。在其他情况下,它可能是一些疯狂的 cthulian 编码,其细节使您能够从 space.

中看到一种新颜色

明确地说:编码不是 属性 数据或位。编码是 数据解释 的属性。通常只有一种正确合理对数据的解释是有意义的,但数据本身就是比特。

C++ 标准不强制要求存储在 wchar_t 中的内容。它确实规定了某些操作将 wchar_t 的内容解释为什么。该部分描述了某些方面如何解释 wchar_t.

中的数据

你的两种解释都不正确。该标准不需要单个 wchar_t 编码,就像它不需要单个 char 编码一样。 codecvt_utf8 方面必须在 UTF-8 和 UCS-2 或 UCS-4 之间转换。 这是真的,甚至 UTF-8、UCS-2 和 UCS-4 在任何语言环境中都不支持作为字符集。

如果 Elemwchar_t 类型并且不够大,无法存储 UCS-2 值,那么 codecvt_utf8 方面的转换操作未定义,因为标准没有说在那种情况下会发生什么。如果它足够大(或者如果你想争论标准要求它必须足够大)那么它只是实现定义了 UCS-2 或 UCS-4 wchar_t 方面生成或消耗的值是在与定义的任何语言环境兼容的编码 wchar_t 编码。

第一种解释条件正确。

如果定义了 __STDC_ISO_10646__ 宏(从 C 导入),那么 wchar_t 是某个版本的 Unicode 的超集。

__STDC_ISO_10646__
An integer literal of the form yyyymmL (for example, 199712L). If this symbol is defined, then every character in the Unicode required set, when stored in an object of type wchar_t, has the same value as the short identifier of that character. The Unicode required set consists of all the characters that are defined by ISO/IEC 10646, along with all amendments and technical corrigenda as of the specified year and month.

看来,如果定义了宏,就可以假设某种UCS4。 (不是 UCS2,因为 ISO 10646 从来没有 16 位版本;ISO 10646 的第一个版本对应于 Unicode 2.0)。

所以如果定义了宏,那么

  • 有"native"wchar_t编码
  • 它是一些 UCS4 版本的超集
  • codecvt_utf8<wchar_t> 提供的转换与本机编码兼容
如果宏未定义,

None 项需要保存。

还有 __STDC_UTF_16____STDC_UTF_32__ 但 C++ 标准没有说明它们的含义。 C 标准表示它们分别表示 char16_tchar32_t 的 UTF-16 和 UTF-32 编码,但在 C++ 中始终使用这些编码。

顺便说一下,mbrtoc32c32rtomb 函数在 char 序列和 char32_t 序列之间来回转换。在 C 中,如果定义了 __STDC_UTF_32__,它们仅使用 UTF-32,但在 C++ 中,UTF-32 始终用于 char32_t。因此,即使 __STDC_ISO_10646__ 未定义 ,也应该可以通过从 UTF-8 到 UTF 在 UTF-8 和 wchar_t 之间进行转换-32 编码 char32_t 到原生编码 char 到原生编码 wchar_t,但我害怕这种复杂的东西。