I have the code below (also on compiler explorer) and I'm confused about why the const Range &
versions don't work. The compiler complains that std::range::begin (and end) don't work.
This is actually an XY problem so I'll also mention how I got here:
I'm writing some unit tests on range adapters and I wrote a matcher in googletest for comparing two ranges:
MATCHER_P(RangeEq, value, "") { return std::ranges::equal(arg, value); }
This worked fine until I got to an adapter that returned a join. As I tried to figure this out I realized that I can't seem to take the range as a const&
and maybe that's the same problem inside the googletest matcher.
#include <algorithm>
#include <iostream>
#include <ranges>
#include <string>
#include <vector>
template <std::ranges::range Range>
auto print1(std::ostream& out, Range&& range) {
out << '[';
auto begin = std::ranges::begin(std::forward<Range>(range));
auto end = std::ranges::end(std::forward<Range>(range));
if (begin != end) {
out << *begin;
++begin;
}
while (begin != end) {
out << ", " << *begin;
++begin;
}
out << ']' << std::endl;
}
template <std::ranges::range Range>
auto print2(std::ostream& out, const Range& range) {
out << '[';
auto begin = std::ranges::begin(range);
auto end = std::ranges::end(range);
if (begin != end) {
out << *begin;
++begin;
}
while (begin != end) {
out << ", " << *begin;
++begin;
}
out << ']' << std::endl;
}
template <std::ranges::range Lhs, std::ranges::range Rhs>
bool equal1(Lhs&& lhs, Rhs&& rhs) {
return std::ranges::equal(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs));
}
template <std::ranges::range Lhs, std::ranges::range Rhs>
bool equal2(const Lhs& lhs, const Rhs& rhs) {
return std::ranges::equal(lhs, rhs);
}
int main() {
std::cout << std::boolalpha;
auto view = std::views::iota(0, 5) | std::views::transform([](auto num) {
return std::views::iota(0, num);
}) |
std::views::join;
print1(std::cout, view);
// print2(std::cout, view); // compile error, why?
auto nums = std::vector<int>{0, 0, 1, 0, 1, 2, 0, 1, 2, 3};
std::cout << equal1(view, nums) << std::endl;
// std::cout << equal2(view, nums) << std::endl; // compile error, why?
return 0;
}
When the reference of the nested range you join is a prvalue range, join_view
needs to temporarily store the entire prvalue range into its internal member, which makes its begin()
not const
-qualified since the iterator will modify this cached member during construction.
That is, in your example join_view
only has non-const
-qualified begin()
, which makes its const
objects not satisfy the range
concept.
A representative non-const
iterable range adaptor is filter_view
, which always does not have the const begin
due to time complexity requirements. However, join_view
conditionally provides it as most range adaptors do (although unfortunately not in your case).