-3

I'm trying to iterate through a text file's lines using ranges:

auto iter_lines(std::ifstream& file)
{
    auto lines = std::ranges::istream_view<char>(file) |
        std::views::lazy_split('\n') |
        std::views::transform([](auto&& line)
        {
            return std::string(line.begin(), line.end());
        });
    return lines;
}

I get a compilation error saying that std::string does not have such a constructor. I tried dropping transform and constructing the strings on the fly instead:

auto iter_lines(std::ifstream& file)
{
    auto lines = std::ranges::istream_view<char>(file) |
        std::views::lazy_split('\n');
    return lines;
}
std::ifstream file("myfile.txt");
for (const auto& line : iter_lines(file))
    std::cout << std::string(line.begin(), line.end()) << std::endl;

I got the same error. Which I found odd, since std::string can accept an iterator range in its constructor. So, I tried to iterate through the characters directly:

std::ifstream file("myfile.txt");
for (const auto& line : iter_lines(file))
{
    for (char c : line)
        std::cout << c;
    std::cout << std::endl;
}

It did compile this way. My initial conclusion was that the type of line.begin() is not the same as the type of line.end() (IntelliSense seemed to agree) and since the constructor of std::string expects two iterators of the same type, it does not compile.

I tried to run the code above and against expectations it printed everything in one line! My final conclusion is that I have no idea how ranges work. I know that istream_view is single-pass, but since I'm using lazy_split I thought it shouldn't be a problem.

What exactly is going on here?

3
  • 4
    Stack Overflow is a question and answer site. You may want to edit this so a single, clear question is being asked. If, for example, you told us what error you were getting, you could ask why you are getting that error. Commented Oct 3 at 19:53
  • 2
    Your first unmentioned "compiler error", for example - merely needs std::views::common, because the std::string constructor is not able to construct from differing ierator/sentinel pairs. I would post this as a clear answer, but you are currently asking for a "thorough explination of" presumably everything you've said here. Commented Oct 3 at 20:03
  • istream_view<char> already skips spaces and \n, so using \n to split the characters make no sense. Commented Oct 4 at 17:47

1 Answer 1

4

As you surmised, the begin() and end() iterators of your line view are likely different types. You can use std::ranges::common_range to verify that. You might try using std::ranges::common_view to adapt your view into one that has iterators of the same type.

But, that will probably not help, as explained by cppreference.com:

https://en.cppreference.com/w/cpp/string/basic_string/basic_string.html

template< class InputIt >
basic_string( InputIt first, InputIt last,
              const Allocator& alloc = Allocator() );

(4) (constexpr since C++20)

  1. Constructs a string with the contents of the range [first, last). Each iterator in [first, last) is dereferenced exactly once.

    ...

    This overload participates in overload resolution only if InputIt satisfies the requirements of LegacyInputIterator.

    ...

https://www.en.cppreference.com/w/cpp/ranges/basic_istream_view.html

The iterator type of basic_istream_view is move-only: it does not meet the LegacyIterator requirements, and thus does not work with pre-C++20 algorithms.

Since ifstream_view iterators do not satisfy LegacyIterator, that means they do not satisfy LegacyInputIterator, either. So you can't pass them to the std::string constructor.

That being said, starting with C++23, std::string has a constructor that accepts a range:

template< container-compatible-range<CharT> R >
> constexpr basic_string( std::from_range_t, R&& rg,
                        const Allocator& = Allocator());

(5) (since C++23)

  1. Constructs a string with the contents of the range rg. Each iterator in rg is dereferenced exactly once.

So, you should be able to do something like this:

#ifdef __cpp_lib_ranges_to_container
return line | std::ranges::to<std::string>();
#else
// use code for earlier compilers ...
#endif

Or:

#ifdef __cpp_lib_containers_ranges
return std::string{std::from_range, line};
#else
// use code for earlier compilers ...
#endif

But, that doesn't help you in C++20, so you are just going to have to iterate the contents of the line view manually until you can upgrade to a newer C++ version.

Sign up to request clarification or add additional context in comments.

1 Comment

A very authoritative poster has strongly suggested that one should never use std::from_range_t directly.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.