3727. reverse_iterator/common_iterator's operator-> should not require the underlying iterator's operator-> to be a const member function

Section: [reverse.iter.elem], [common.iter.access] Status: NAD Submitter: Hewill Kang Opened: 2022-06-27 Last modified: 2022-11-30 17:59:24 UTC

Priority: Not Prioritized

View all other issues in [reverse.iter.elem].

View all issues with NAD status.


For non-pointer types, reverse_iterator::operator-> requires that the Iterator must have an operator->() with const-qualifier, whereas in the Effects clause, it always invokes the non-const object's operator->().

common_iterator::operator-> also requires that I::operator->() must be const-qualified, which seems reasonable since the return type of get<I>(v_) is const I&. However, LWG 3672 makes common_iterator::operator->() always return a value, which makes it unnecessary to detect the constness of I::operator->(), because it will be invoked with a non-const returned object anyway.

I think we should remove this constraint as I don't see the benefit of doing that. Constraining iterator's operator->() to be const and finally invoking non-const overload doesn't feel right to me either. In <ranges>, the exposition-only constraint has-arrow (26.5.2 [range.utility.helpers]) for operator->() does not require that the underlying iterator's operator->() to be const, we should make them consistent, and I believe this relaxation of constraints can bring some value.


This issue's second part of the resolution actually depends on 3672 being applied. But note that the reference wording below is still N4910.

[2022-08-23; Reflector poll: NAD]

Implicit variations apply to those requires-expressions, so calling as non-const (and rvalue) is fine. The PR actually loses that property and makes those overloads truly underconstrained. Motivation for relaxing it is vague. As for consistency, we should fix has-arrow instead.

[2022-11-30 LWG telecon. Status changed: Tentatively NAD → NAD.]

Proposed resolution:

This wording is relative to N4910.

  1. Modify [reverse.iter.elem] as indicated:

    constexpr pointer operator->() const
      requires (is_pointer_v<Iterator> ||
                requires(const Iterator i) { i.operator->(); });

    -2- Effects:

    1. (2.1) — If Iterator is a pointer type, equivalent to: return prev(current);

    2. (2.2) — Otherwise, equivalent to: return prev(current).operator->();

  2. Modify [common.iter.access] as indicated:

    constexpr decltype(auto) operator->() const
      requires see below;

    -3- The expression in the requires-clause is equivalent to:

      indirectly_readable<const I> &&
      (requires(const I& i) { i.operator->(); } ||
      is_reference_v<iter_reference_t<I>> ||
      constructible_from<iter_value_t<I>, iter_reference_t<I>>)

    -4- Preconditions: holds_alternative<I>(v_) is true.

    -5- Effects:

    1. (5.1) — If I is a pointer type or if the expression get<I>(v_).operator->() is well-formedrequires(I i) { i.operator->(); } is true, equivalent to: return get<I>(v_);

    2. (5.2) — Otherwise, if iter_reference_t<I> is a reference type, equivalent to:

        auto&& tmp = *get<I>(v_);
        return addressof(tmp);
    3. (5.3) — Otherwise, equivalent to: return proxy(*get<I>(v_)); where proxy is the exposition-only class:

        class proxy {
          iter_value_t<I> keep_;
          constexpr proxy(iter_reference_t<I>&& x)
            : keep_(std::move(x)) {}
          constexpr const iter_value_t<I>* operator->() const noexcept {
            return addressof(keep_);