cmannett85
cmannett85

Reputation: 22346

Using C++20 ranges with istreambuf_iterator

I can't compile a (very contrived) C++ ranges example:

#include <ranges>
#include <fstream>
#include <vector>

template <typename R>
auto populate(R&& range)
{
    return std::vector<char>(range.begin(), range.end());
}

int main(int argc, char* argv[]) {
    auto stream = std::ifstream{"/etc/hosts"};

    const auto is_odd = [](auto i) { return (i % 2) == 1; };
    const auto hosts_data = populate(
        std::ranges::subrange{std::istreambuf_iterator<char>{stream},
                              std::istreambuf_iterator<char>{}} |
        std::views::filter(is_odd)
    );

    return EXIT_SUCCESS;
}

Results in:

<source>:19:35:   required from here
<source>:9:17: error: no matching function for call to 'std::vector<char>::vector(std::ranges::filter_view<std::ranges::subrange<std::istreambuf_iterator<char, std::char_traits<char> >, std::istreambuf_iterator<char, std::char_traits<char> >, std::ranges::subrange_kind::unsized>, main(int, char**)::<lambda(auto:13)> >::_Iterator, std::ranges::filter_view<std::ranges::subrange<std::istreambuf_iterator<char, std::char_traits<char> >, std::istreambuf_iterator<char, std::char_traits<char> >, std::ranges::subrange_kind::unsized>, main(int, char**)::<lambda(auto:13)> >::_Iterator)'

    9 |     return std::vector<char>(range.begin(), range.end());
      |   

From further experimentation, it appears that the use of istreambuf_iterator is causing the problem, but I don't know why. Can anyone help?

Upvotes: 4

Views: 508

Answers (1)

Barry
Barry

Reputation: 302718

This makes me sad.


filter_view::iterator is specified as, from [range.filter.iterator]:

constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires forward_range<V>;

Notably, a subrange of istreambuf_iterator<char> is not a forward_range - it is an input_range (because istreambuf_iterator is only an input_iterator).

Thus, the postfix operator++ returns void instead of iterator.

As a result of that, our particular filter_view::iterator does not meet the requirements of cpp17-iterator as specified in [iterator.traits]/2:

template<class I>
concept cpp17-iterator =
  copyable<I> && requires(I i) {
    {   *i } -> can-reference;
    {  ++i } -> same_as<I&>;
    { *i++ } -> can-reference; // we fail this one
  };

The constructor you're trying to call in vector is specified as, from [vector.overview]:

template<class InputIterator>
  constexpr vector(InputIterator first, InputIterator last, const Allocator& = Allocator());

Which means, from [sequence.reqmts]/13:

If the constructor

template<class InputIterator>
  X(InputIterator first, InputIterator last,
    const allocator_type& alloc = allocator_type());

is called with a type InputIterator that does not qualify as an input iterator, then the constructor shall not participate in overload resolution.

And our iterator type does not qualify as an input iterator because the result of postfix increment isn't dereferencable (see also this table).


At least this is just an input iterator, so it's not like there's any gain in efficiency to be had anyway, so you can just change your implementation of populate() to if constexpr based on whether you actually get a cpp17-iterator out of it (based on checking is_constructible) and just loop/push_back otherwise.

For some reason ranges::to from range-v3 doesn't compile here but I think that one's a compiler issue.

Upvotes: 6

Related Questions