如何在 C++ 中获得 std::u8string 的正确长度?

How to get correct length of std::u8string in C++?

如何获得 std::u8string 的正确长度? (在 C++20 中) 我已尝试使用以下代码打印不正确的长度值,该值可能 returns 代码点数的值。

如何获得我期望的 7 个字符的正确值?

int main() {
    const char8_t* s = u8"Hello";
    auto st = std::u8string(s);
    std::cout << st.size() << std::endl;
}
就大多数 C++ 函数而言,

A u8string 实际上是一个字节序列。因此 size() 给你 13 (48 65 6c 6c 6f f0 9f 98 83 f0 9f 98 83)。 "" ("SMILING FACE WITH OPEN MOUTH" U+1F603) 被编码为 4 个元素 f0 9f 98 83。您也会在 [i]substr 等中看到这一点。

知道是UTF-8,可以统计一下Unicode码位的个数。您可以使用 u32string 这是代码点。我不相信 C++ 有直接在开箱即用的 u8string 上这样做的功能:

size_t count_codepoints(const std::u8string &str)
{
    size_t count = 0;
    for (auto &c : str)
        if ((c & 0b1100'0000) != 0b1000'0000) // Not a trailing byte
            ++count;
    return count;
}

然而,这可能仍然不是人们认为的 "number of character"。这是因为多个代码点可能用于表示单个可见字符 "combining characters"。其中一些也有 "precomposed" 形式,组合代码点的顺序可能不同,导致 "normal forms" 和比较 Unicode 字符串的问题。例如“Á”可能是 "LATIN CAPITAL LETTER A WITH ACUTE' (U+00C1)",它是 UTF-8 C3 81,或者它可能有一个正常的 "A" 和一个 "COMBINING ACUTE ACCENT (U+0301)",它是两个代码点和 3 个 UTF- 8 个字节 41 CC 81.

unicode.org 中的每个 Unicode 版本都有表格,可让您正确处理和转换组合字符(以及 upper/lower 大小写转换之类的东西),但它们非常广泛,您需要编写一些代码来处理它们。第 3 方库(我认为 Linux 主要使用 ICU)或 OS 函数(Window 有一堆 API)也提供各种实用程序。

值得注意的是,您可以 运行 在许多其他 cases/languages 中解决这些问题,而不仅仅是 C++。例如Java脚本、Java 和 .NET,以及 Windows C/C++ API(本质上是 Windows 上的 wchar_t)使用具有 "surrogate pairs" 的 UTF-16 字符串用于一些代码点,许多函数实际上计算 UTF-16 元素,而不是代码点。

标准的 C++ 答案是将字符串从 utf8 转换为 utf32,然后检查大小。

令人担忧的是,从 c++17 开始,std::wstring_convert 现已弃用。我不知道替代品是什么。

#include <string>
#include <iostream>
#include <cstdlib>
#include <locale>
#include <codecvt>

auto convert(std::u8string input) -> std::u32string
{
    auto first = reinterpret_cast<const char*>(input.data());
    auto last = first + input.size();

    auto result = std::u32string();

    std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> ucs4conv;
    try
    {
        result = ucs4conv.from_bytes(first, last);
    }
    catch(const std::range_error& e) {
        last = first + ucs4conv.converted();
        std::clog << "UCS4 failed after consuming " << std::dec << std::distance(first, last) <<" characters:\n";
        result = ucs4conv.from_bytes(first, last);
    }

    return result;
}

int main() {
    const char8_t* s = u8"Hello";
    auto st = std::u8string(s);
    std::cout << "bytes      : " << st.size() << std::endl;

    auto ws = convert(st);
    std::cout << "wide chars : " << ws.size() << std::endl;
}

预期输出:

bytes      : 13
wide chars : 7

https://godbolt.org/z/Z0a6bb

其他答案已经建议了计算代码点数量的方法,如果这确实是您的用例所需要的。我添加这个答案是为了说明代码点长度可能不是您想要的。

实际上,我不会亲自说明这一点。相反,我将向一个优秀的博客 post 提供一个 link 来解释这些问题,以便您可以评估您实际需要的信息。

https://hsivonen.fi/string-length