Search code examples
c++splitc++20std-ranges

Difficulty using std::views::split to populate a std::vector in C++


I'm facing an issue while attempting to populate a std::vector with the results of std::views::split in C++. Specifically, I can successfully construct a std::string from the items of std::ranges::split_view (as shown in the first loop of the code below), but I encounter difficulties when trying to use an iterator to create a std::vector directly.

// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>

int main()
{
    std::string rg{ "1, 2, 3, 1, 2, 3, 4, 5, 6 "};

    // Successful construction of std::string from std::ranges::split_view
    for (const auto& subrange : rg | std::views::split('3'))
    {
        std::string value(subrange.begin(), subrange.end());
        std::cout << value << '\n';
    }

    // Issue arises here when attempting to create std::vector
    auto parts = rg | std::views::split('3');
    auto asVector = std::vector<std::string>(parts.begin(), parts.end());

    // The following loop results in a compilation error
    for (const auto& subrange : asVector)
    {
        std::string value(subrange.begin(), subrange.end());
        std::cout << value << '\n';
    }
}

Compilation Error:

Error   C2665   'std::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string': no overloaded function could convert all the argument types    
splitView   C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.33030\include\xutility    241

I appreciate any insights or solutions to help me resolve this issue.


Solution

  • The value type of rg | std::views::split('3') is subrange<string::iterator, string::iterator>, and string cannot be constructed through it, that is, is_constructible<string, subrange<string::iterator, string::iterator> is false, so the compilation fails.

    The ideal way is to use C++23 ranges::to:

    auto asVector = std::ranges::to<std::vector<std::string>>(parts);
    

    which is applied recursively, so the string is first constructed from the subrange via (nested) ranges::to, which in turn invokes its range constructor string(from_range_t, R&&), and then the vector is subsequently constructed.

    In C++20 you still need to manually convert the original subrange to std::string

    auto parts = rg | std::views::split('3') 
                    | std::views::transform([](auto r) {
                        return std::string(r.data(), r.size());
                      });
    auto asVector = std::vector(parts.begin(), parts.end());