This code does not compile with range-v3 0.10.0 (or with master). It does compile with range-v3 0.9.1.
#include "range/v3/all.hpp"
struct double_it {
using value_type = double;
double x;
double& operator*() { return x; }
const double& operator*() const { return x; }
};
static_assert(ranges::readable<double_it>);
Which version is right ? In master, a type I is readable only if same_as<iter_reference_t<I const>, iter_reference_t<I>>. I don't understand why the return type of operator* should be the same as the one of operator* const.
Note: issue submitted on github here.
Take a look at P1878, which has all the rationale for this late design change. Iterators represent indirections. In C++, const-ness is shallow, meaning it doesn't follow indirections. Whether you dereference an int* or an int*const, the result you get back is the same: int&. The top-level const doesn't -- and shouldn't -- matter. Iterators do as the pointers do. They must, because a pointer is a valid model of the iterator concept.
To make this distinction more explicit, in C++20, the readable concept is called indirectly_readable.
TL;DR: Don't make the reference type of your iterator depend on the const-ness of the iterator itself.
This seems to be at the very least an inconsistency in range-v3 as compared to the Ranges TS working draft.
There's a quite sparse description as for why these same_as predicates were added to to the readable concept for iterators in range-v3 issue 1449.
Get the range library compiling with msvc 19.24.28319 with /std:c++17
...
- CPP_concept_fragment(readable_, (I), + CPP_concept_fragment(readable_, + requires (/*I const i*/) // + ( + // { *i } -> same_as<iter_reference_t<I>>; + // { iter_move(i) } -> same_as<iter_rvalue_reference_t<I>>; + 0 + ) && + same_as<iter_reference_t<I const>, iter_reference_t<I>> && + same_as<iter_rvalue_reference_t<I const>, iter_rvalue_reference_t<I>> && common_reference_with<iter_reference_t<I> &&, iter_value_t<I> &> && common_reference_with<iter_reference_t<I> &&, iter_rvalue_reference_t<I> &&> && common_reference_with<iter_rvalue_reference_t<I> &&, iter_value_t<I> const &> );
It seems the same_as predicates in the concept's implementation are intended to implement the requirement of:
// { *i } -> same_as<iter_reference_t<I>>; // { iter_move(i) } -> same_as<iter_rvalue_reference_t<I>>;
Which were present (as comments in range/v3/iterator/concepts.hpp) even prior to this implementation change.
However, afaics, none of these requirements are present in the working draft of [iterators.readable] of Ranges TS (nor in the current HEAD of ericniebler/stl2 from which the previous linked draft is generated).
[iterators.readable] Concept Readable
The Readable concept is satisfied by types that are readable by applying operator* including pointers, smart pointers, and iterators.
template <class In> concept bool Readable = requires { typename value_type_t<In>; typename reference_t<In>; typename rvalue_reference_t<In>; } && CommonReference<reference_t<In>&&, value_type_t<In>&> && CommonReference<reference_t<In>&&, rvalue_reference_t<In>&&> && CommonReference<rvalue_reference_t<In>&&, const value_type_t<In>&>;
It may be a good idea to report this as an issue to, at the very least, sort out why the range-v3 implementation seemingly differs from [iterators.readable] of Ranges TS.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With