`std::views::split` on MSVC
Asked Answered
G

3

5

I want to split a string by a token with std::views::split, and for each retrieved substring invoke the std::from_chars function. Here is an MRE (https://godbolt.org/z/1K71qo9s4) that compiles successfully on GCC but fails to compile on 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 << ' ';
    }
}

What strange is that according to cppreference, the behavior of std::views::split was changed in P2210R2, providing base() function which effect is

Equivalent to return cur_

Then, according to compiler support page, the P2210R2 is supported in MSVC since 19.31, so the example should work.

Glendaglenden answered 26/7, 2023 at 10:36 Comment(0)
D
3

If this were working how you think it does, i.begin().base() should be a std::string::iterator, which has to be const char* (or at least convertible to a const char*) to pass to std::from_chars.

Looks like libstdc++ also doesn't implement this properly either, and i.begin() is returning a std::string::iterator instead of a std::ranges::views::split_view<std::string>::iterator. It just so happens that libstdc++'s std::string::iterator has a function called base() that returns a pointer.

You have to get a const char* pointer anyways, which you can do with std::to_address:

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

Or something like:

    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 );
Derision answered 26/7, 2023 at 11:13 Comment(0)
K
4

source | std::views::split('%') produces a nested range whose value_type is subrange, so in your example, the type of i is actually a subrange containing two iterators of source, which is string::iterator.

Thus, i.begin() calls subrange::begin(), which returns the underlying string::iterator object. For MSVC-STL, string::iterator does not provide a base() member, because this is an implementation-defined type, which only requires to conform to [container.requirements] specification, which does not include providing a base(). This is why your code fails.

However, you can use std::to_address to get the raw pointer

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

What strange is that according to cppreference, the behavior of std::views::split was changed in P2210R2, providing base() function which effect is

You are misunderstanding the usage. split_view::iterator does provide a base(), so (source | std::views::split('%')).begin().base() still works.

Klayman answered 26/7, 2023 at 11:27 Comment(0)
D
3

If this were working how you think it does, i.begin().base() should be a std::string::iterator, which has to be const char* (or at least convertible to a const char*) to pass to std::from_chars.

Looks like libstdc++ also doesn't implement this properly either, and i.begin() is returning a std::string::iterator instead of a std::ranges::views::split_view<std::string>::iterator. It just so happens that libstdc++'s std::string::iterator has a function called base() that returns a pointer.

You have to get a const char* pointer anyways, which you can do with std::to_address:

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

Or something like:

    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 );
Derision answered 26/7, 2023 at 11:13 Comment(0)
B
3

Both answers here seem to be overcomplicating things. In:

for ( auto i : source | std::views::split('%') )

i is a ranges::subrange<string::iterator>. That's a contiguous, sized range. That means that subrange provides data() and size() for you, so this works:

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

You don't need to first convert this to a span and you definitely don't need std::to_address.

Bourbon answered 26/7, 2023 at 16:2 Comment(1)
Great catch. When I first saw the OP I was blinded by the P2584's Tony table that popped into my mind.Crosslink

© 2022 - 2024 — McMap. All rights reserved.