Reputation: 22346
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
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