为什么 std::views::split() 编译但不使用未命名字符串文字作为模式进行拆分?

kha*_*aos 23 c++ split string-literals std-ranges c++23

std::views::split()获取未命名的字符串文字作为模式时,它不会分割字符串,但可以很好地处理未命名的字符文字。

#include <iomanip>
#include <iostream>
#include <ranges>
#include <string>
#include <string_view>

int main(void)
{
    using namespace std::literals;

    // returns the original string (not splitted)
    auto splittedWords1 = std::views::split("one:.:two:.:three", ":.:");
    for (const auto word : splittedWords1)
        std::cout << std::quoted(std::string_view(word));
    
    std::cout << std::endl;

    // returns the splitted string
    auto splittedWords2 = std::views::split("one:.:two:.:three", ":.:"sv);
    for (const auto word : splittedWords2)
        std::cout << std::quoted(std::string_view(word));
    
    std::cout << std::endl;

    // returns the splitted string
    auto splittedWords3 = std::views::split("one:two:three", ':');
    for (const auto word : splittedWords3)
        std::cout << std::quoted(std::string_view(word));
    
    std::cout << std::endl;

    // returns the original string (not splitted)
    auto splittedWords4 = std::views::split("one:two:three", ":");
    for (const auto word : splittedWords4)
        std::cout << std::quoted(std::string_view(word));
    
    std::cout << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

请参阅@ godbolt.org直播。

我知道字符串文字始终是左值。但尽管如此,我还是遗漏了一些将一切联系在一起的重要信息。为什么我可以传递我想要拆分为未命名字符串文字的字符串,而当我对模式执行相同操作时它会失败(如:返回原始字符串的范围)?

康桓瑋*_*康桓瑋 34

字符串文字始终以空终止符结尾,因此":.:"实际上是最后一个元素为\0且大小为 的4范围。

由于原始字符串不包含这样的模式,因此不会被分割。

在处理 C++20 范围时,我强烈建议使用string_view原始字符串文字来代替,这<ranges>可以很好地处理并避免容易出错的空终止符问题。

  • 我无法表达我有多讨厌这是正确的,并且如果没有额外的语言支持,库就无法很好地处理这个问题。 (19认同)
  • @AnArrayOfFunctions,我不想最终产生一堆噪音,但我的问题是编写一个具有直观含义的模式真的很容易(例如,`":"`意思是分割像所有其他语言一样的冒号)并且可以合理地通过代码审查,除非有人专门寻找这个,但实际上有一个完全不同的含义,你几乎永远不会想要(在冒号上分割,后跟 \0)。将字符串文字编写为 split 调用的模式是一种常见的用例,需要额外的装饰,并且会在没有任何装饰的情况下默默地做错误的事情。 (3认同)

Bar*_*rry 13

这个答案是完全正确的,我只想添加一些可能有趣的附加注释。


首先,如果您用于{fmt}打印,则更容易看到发生了什么,因为您也不必编写自己的循环。你可以这样写:

fmt::print("{}\n", rv::split("one:.:two:.:three", ":.:"));
Run Code Online (Sandbox Code Playgroud)

将输出(这是一系列字符范围的默认输出):

[[o, n, e, :, ., :, t, w, o, :, ., :, t, h, r, e, e, ]]
Run Code Online (Sandbox Code Playgroud)

在 C++23 中,将有一种方法可以直接指定此打印为字符串范围,但尚未添加{fmt}。同时,由于split保留了初始范围类别,因此您可以添加:

[[o, n, e, :, ., :, t, w, o, :, ., :, t, h, r, e, e, ]]
Run Code Online (Sandbox Code Playgroud)

进而:

auto to_string_views = std::views::transform([](auto sr){
    return std::string_view(sr.data(), sr.size());
});
Run Code Online (Sandbox Code Playgroud)

印刷:

["one:.:two:.:three\x00"]
Run Code Online (Sandbox Code Playgroud)

请注意明显的尾随零。同样,接下来的三次尝试格式如下:

["one", "two", "three\x00"]
["one", "two", "three\x00"]
["one:two:three\x00"]
Run Code Online (Sandbox Code Playgroud)

事实上,我们可以清楚地看到\x00有助于追踪问题。


接下来,考虑以下之间的区别:

fmt::print("{}\n", std::views::split("one:.:two:.:three", ":.:") | to_string_views);
Run Code Online (Sandbox Code Playgroud)

["one:.:two:.:three\x00"]
Run Code Online (Sandbox Code Playgroud)

我们通常认为它们是等价的,但它们……并不完全是等价的。在后一种情况下,图书馆必须捕获并存储这些值 - 这涉及到衰减它们。在这种情况下,因为":.:"衰变成char const*,所以它不再是传入字符串文字的有效模式。所以上面的内容实际上并没有编译。

现在,如果它既能编译又能正常工作,那就太好了。不幸的是,在语言中无法区分字符串文字(您不想包含空终止符)和数组char(您想要包含整个数组)。因此,至少,使用后一种表述,您可能会得到无法编译的错误结果。至少 - “不编译”比“编译并做了一些与我预期完全不同的事情”更好?


演示