dani
dani

Reputation: 3887

How to split a std::string into a range (v3) of std::string_views?

I need to split a std::string at all spaces. The resulting range should however transform it's element to std::string_views. I'm struggling with the "element type" of the range. I guess, the type is something like a c_str. How can I transform the "split"-part into string_views?

#include <string>
#include <string_view>
#include "range/v3/all.hpp"

int main()
{
    std::string s = "this should be split into string_views";

    auto view = s 
            | ranges::view::split(' ') 
            | ranges::view::transform(std::string_view);
}

Upvotes: 17

Views: 16730

Answers (3)

Holt
Holt

Reputation: 37616

This answer is for and .

For a / answer using , see Peng's answer.


(One of) the problem here is that ranges::view::split returns a range of ranges, and you cannot construct a std::string_view directly from a range.

You want something like this:

auto view = s
    | ranges::views::split(' ')
    | ranges::views::transform([](auto &&rng) {
        const auto size = ranges::distance(rng);
        // &*rng.begin() is UB if the range is empty, so we need
        // to check before using it
        return size ? std::string_view(&*rng.begin(), size) : std::string_view();
});

There might be a better/easier way to do this but:

  • &*rng.begin() will give you the address of the first character of the chunk in the original string.
  • ranges::distance(rng) will give you the number of characters in this chunk. Note that this is slower than ranges::size but required here because we cannot retrieve the size of rng in constant time.

Upvotes: 33

sam
sam

Reputation: 856

//C++20:


#include <iostream>
#include <string>
#include <ranges>

    int main() {
    std::string name("HE LL O");

    auto splitView = std::ranges::views::split(name, ' ');

    // Iterate over each subrange resulting from the split
    for (const auto& subrange : splitView)
    {
        // Create a string_view from the subrange for proper output
        std::cout << std::string_view(subrange.begin(), subrange.end()) << '\n';
    }

    return 0;
}

// second solution

#include <iostream>
#include <ranges>
int main()
{

   std::string greetings("HE LL O");
    auto splitView = std::ranges::views::split(greetings,' ')
                    | std::ranges::views::transform([](const auto& subrange){
                        return std::string_view(subrange.begin(),subrange.end());
   });

    for (const auto& substr : splitView)
    {
        std::cout << substr << '\n';
    }
}

Upvotes: 0

Peng
Peng

Reputation: 1491

Now that we are in the golden era of C++23, we have a bunch of new ways supported by the standard library:

auto view = s | std::views::split(' ')
              | std::views::transform([](auto rng) { 
                  return std::string_view(rng.data(), rng.size());  // C++20 (subrange::data(), size())
                  // return std::string_view(rng.begin(), rng.end());  // C++20
                  // return std::string_view(rng);  // C++23
            });

But I don't see any point to transform a view of subranges (the output of split()) to a view of string_views.

If your final objective is to split at space and concatenate the subranges into a string, it can be easily done as follows:

auto str = s | std::views::split(' ') 
             | std::views::join
             | std::ranges::to<std::string>();  // requires C++23

Upvotes: 2

Related Questions