3

I'd like to use Ranges (I use range-v3 implementation) to read a input stream that is a comma separated list of numbers. That is trivial to do without ranges but... This is what I thought was the straight-forward way to solve it:

auto input = std::istringstream("42,314,11,0,14,-5,37");
auto ints = ranges::istream_view<int>(input) | ranges::view::split(",");
for (int i : ints)
{
    std::cout << i << std::endl;
}

But this fails to compile. I've tried a number of variations of this but nothing seem to work, I guess this is wrong in several ways. Can someone please enlighten me what I am doing wrong and explain how this should be done instead?

Thanks in advance!

bamse
  • 55
  • 5
  • 1
    `this fails so bad` - what does it exactly mean when something "fails so bad"? Does it compile? Does the resulting binary from compilation execute? Does it output some strange numbers? What does it output? Does it throw an exception? How to detect when something "fails so bad"? – KamilCuk Dec 02 '19 at 22:31
  • @KamilCuk yes, you are right. I have clarified that it fails to compile. – bamse Dec 02 '19 at 22:50

2 Answers2

4

What

ranges::istream_view<int>(input)

does is produce a range that is the rough equivalent of this coroutine (even if you don't understand C++20 coroutines, hopefully this example is simple enough that it gets the point across):

generator<int> istream_view_ints(istream& input) {
    int i;
    while (input >> i) {  // while we can still stream int's out
       co_yield i;        // ... yield the next int
    }
}

Two important points here:

  1. This is range of ints, so you cannot split it on a string.
  2. This uses the normal stream >>, which does not allow you to provide your own delimiter - it only stops at whitespace.

Altogether, istream_view<int>(input) gives you a range of ints that, on your input, consists of a single int: just 42. The next input would try to read in the , and fail.


In order to get a delimited input, you can use getlines. That will give you a range of string with the delimiter you provide. It uses std::getline internally. Effectively, it's this coroutine:

generator<string> getlines(istream& input, char delim = '\n') {
    string s;
    while (std::getline(input, s, delim)) {
        co_yield s;
    }
}

And then you need to convert those strings to ints. Something like this should do the trick:

auto ints = ranges::getlines(input, ',')
          | ranges::view::transform([](std::string const& s){ return std::stoi(s); });
metalfox
  • 6,301
  • 1
  • 21
  • 43
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you very much @Barry for enlighten me! :-) It is a pity that it is not easy, or even possible(?), to simply set a custom delimiter on the istream instead of it only separating its input on whitespace. I was not aware of ranges::getlines, but feeding it with the istream solved my problem nicely, just as shown by your example. – bamse Dec 03 '19 at 15:16
2
std::string input = "42,314,11,0,14,-5,37";
auto split_view = ranges::view::split(input, ",");

would produce a range of ranges:

{{'4', '2'}, {'3', '1', '4'}, {'1', '1'}, {'0'}, {'1', '4'}, {'-', '5'}, {'3', '7'}}.

so you might do:

std::string input = "42,314,11,0,14,-5,37";
auto split_view = ranges::view::split(input, ",");
for (auto chars : split_view) {
    for (auto c : chars) {
        std::cout << c;
    }
    std::cout << std::endl;
}
Barry
  • 286,269
  • 29
  • 621
  • 977
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks @Jarod42 for your answer. I was asking for how to handle istream input, not string input. But your example showing a string split resulting in a range of ranges enlightened me anyway, even though it did not answer my question. :-) – bamse Dec 03 '19 at 15:21
  • I indeed only spot and explain the issue, without giving solution. (I originally though about *reduce* the subrange to `std::string` to after *transform* to `int`, but `ranges::getlines` proposed by Barry is superior). – Jarod42 Dec 03 '19 at 15:44