在MSVC上使用`std::views::split`

5
我想使用std::views::split按照标记将字符串拆分,并对每个获取的子字符串调用std::from_chars函数。 这里有一个最小可复现示例(https://godbolt.org/z/1K71qo9s4),在GCC上成功编译但在MSVC上无法编译。
#include <iostream>
#include <ranges>
#include <string>
#include <charconv>
 
 
int main()
{
    std::string source{"10%15%20%35"};
    for ( auto i : source | std::views::split('%') )
    {
        int x;
        std::from_chars( i.begin().base(), i.end().base(), x );
        std::cout << x << ' ';
    }
}

根据cppreference所述,奇怪的是std::views::split的行为在P2210R2中发生了变化,提供了base()函数,其效果是

等同于return cur_

然后,根据compiler support page所述,自19.31版本起,MSVC支持P2210R2,因此示例应该可以正常工作。
3个回答

4

source | std::views::split('%') 产生一个嵌套范围,其value_typesubrange,所以在你的例子中,i的类型实际上是一个包含两个source的迭代器的subrange

因此,i.begin()调用subrange::begin(),它返回底层的string::iterator对象。对于MSVC-STL来说,string::iterator没有提供base()成员,因为这是一个实现定义的类型,只需要符合[container.requirements]规范,该规范不包括提供base()。这就是为什么你的代码失败的原因。

然而,你可以使用std::to_address来获取原始指针。

std::from_chars(std::to_address(i.begin()), std::to_address(i.end()), x);

根据cppreference的说法,奇怪的是,P2210R2中更改了std::views::split的行为,提供了base()函数,其作用是什么。
你误解了用法。split_view::iterator确实提供了base(),所以(source | std::views::split('%')).begin().base()仍然有效。

3
如果这个工作方式与你想的一样,i.begin().base() 应该是一个 std::string::iterator,它必须是一个 const char*(或者至少可以转换为 const char*),以便传递给 std::from_chars
看起来 libstdc++ 也没有正确实现这个,i.begin() 返回的是一个 std::string::iterator 而不是一个 std::ranges::views::split_view<std::string>::iterator。碰巧 libstdc++ 的 std::string::iterator 有一个名为 base() 的函数返回一个指针。
无论如何,你都需要得到一个 const char* 指针,你可以使用 std::to_address 来实现:
    std::from_chars( std::to_address(i.begin()), std::to_address(i.end()), x );

或者类似的东西:
    std::from_chars( std::ranges::data(i), std::ranges::data(i) + std::ranges::size(i), x );

// Or, since span is constructible from ranges

for (std::span<const char> i : source | std::views::split('%')) {
    std::from_chars( i.data(), i.data() + i.size(), x );

3
这里的两个答案似乎都把事情复杂化了。在:
for ( auto i : source | std::views::split('%') )

i 是一个 ranges::subrange<string::iterator>。那是一个连续的、有大小的范围。这意味着 subrange 为您提供了 data()size(),所以这样可以工作:

for ( auto i : source | std::views::split('%') )
{
    int x;
    std::from_chars( i.data(), i.data() + i.size(), x );
    std::cout << x << ' ';
}

你不需要先将此转换为,也绝对不需要使用std::to_address。

很棒的发现。当我第一次看到这个 OP 时,我的脑海里浮现出了 P2584 中的 Tony 表格,让我眼前一亮。 - 康桓瑋
太棒了。当我第一次看到这个OP时,我脑海中浮现出P2584的Tony表,让我眼前一亮。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接