Readable types with prvalue reference types erroneously model Writable (original) (raw)
Consider:
struct MakeString { using value_type = std::string; std::string operator*() const { return std::string(); } };
static_assert(!Writable<MakeString, std::string>()); // FAILS
This is a huge usability problem, since it permits users to, e.g., pass a range to sort that is not, in fact, sortable. The Ranges TS inherited this problem from the Palo Alto Report (N3351), which also has this bug. Fixing this will be tricky easy :-) without messing up proxy iterators.
EDIT: See ericniebler/range-v3#573
Resolution Discussion:
One fix would be to require that *o return a true reference, but that breaks when *o returns a proxy reference. The trick is in distinguishing between a prvalue that is a proxy from a prvalue that is just a value. The trick lies in recognizing that a proxy always represents a (logical, if not physical) indirection. As such, adding a const to the proxy should not effect the mutability of the thing being proxied. Further, if decltype(*o) is a true reference, then adding const to it has no effect, which also does not effect the mutability. So the fix is to add const to decltype(*o), const_cast *o to that, and then test for writability.
Proposed Resolution
Accept the wording in P0547R1 (duplicated below).
(Includes the resolution for #387.)
Change the definition of Writable ([iterators.writable]) as follows:
template <class Out, class T> concept bool Writable() {
- return requires(Out& o, T&& t) {
- return requires(Out&& o, T&& t) { *o = std::forward(t); // not required to be equality preserving
- *std::forward(o) = std::forward(t); // not required to be equality preserving
- const_cast<const reference_t&&>(*o) =
std::forward<T>(t); // not required to be equality preserving- const_cast<const reference_t&&>(*std::forward(o)) =
}; }std::forward<T>(t); // not required to be equality preserving
In [iterators.writable] paragraphs 2.1 and 3, replace "the assignment" with "either above assignment":
(2.1) — If Readable() && Same<value_type_t, decay_t>() is satisfied, then *o after
the
3 After evaluatingany above assignment is equal to the value of E before the assignment.
the
any above assignment expression, o is not required to be dereferenceable.