3952. iter_common_reference_t does not conform to the definition of indirectly_readable

Section: 25.2 [iterator.synopsis], [const.iterators.alias], [const.iterators.iterator] Status: New Submitter: Hewill Kang Opened: 2023-06-28 Last modified: 2023-10-30 16:39:42 UTC

Priority: 3

View all other issues in [iterator.synopsis].

View all issues with New status.


The indirectly_readable concept ( [iterator.concept.readable]) requires iter_reference_t<In>&& and iter_value_t<In>& to model common_reference_with, which ensures that the input iterator always has a common reference type.

However, iter_common_reference_t for computing such types is defined as common_reference_t<iter_reference_t<T>, indirect-value-t<T>>.

It is unclear why the formula here drop the && part of iter_reference_t<In>, but theoretically it is not completely equivalent to the former, for example:

#include <iterator>

struct Ref {
  Ref(const Ref&) = delete;

struct Val {
  operator const Ref&() const &;

struct I {
  using value_type = Val;
  using difference_type = int;
  Ref operator*() const;
  I& operator++();
  I operator++(int);

using reference  = std::iter_reference_t<I>;
using value_type = std::iter_value_t<I>;
static_assert(std::same_as<std::common_reference_t<reference&&, value_type&>, const Ref&>);
static_assert(std::same_as<std::common_reference_t<reference  , value_type&>,       Ref >);

std::iter_value_t<I> val;
std::iter_common_reference_t<I> cr = val; // failed

In the above example, input_iterator ensures that the iterator's lvalue value type and rvalue reference type can be bound to its common reference type, namely const Ref&, but the type calculated by iter_common_reference_t is Ref, which cannot be bound by both.

The proposed resolution re-adds the && to iter_reference_t<In> in formulas of similar form to conform to the definition of indirectly_readable.

[2023-10-30; Reflector poll]

Set priority to 3 after reflector poll. "NAD - This can easily lead to dangling references. This only matters if iter_reference_t isn't a language reference type, and the change causes common_reference to produce a language reference type. So binding to the common reference requires a temporary. That's not going to work if the type is used as a return type (as the const-cases are). As written I think it also causes significant damage to constant-iterator."

Proposed resolution:

This wording is relative to N4950.

  1. Modify 25.2 [iterator.synopsis], header <iterator> synopsis, as indicated:

    template<indirectly_readable T>
      using iter_common_reference_t =                                                 // freestanding
        common_reference_t<iter_reference_t<T>&&, indirect-value-t<T>>;
  2. Modify [const.iterators.alias] as indicated:

    template<indirectly_readable It>
      using iter_const_reference_t =
        common_reference_t<const iter_value_t<It>&&, iter_reference_t<It>&&>;
  3. Modify [const.iterators.iterator] as indicated:

    namespace std {
      template<class I>
        concept not-a-const-iterator = see below;                   // exposition only
      template<indirectly_readable I>
        using iter-const-rvalue-reference-t =                       // exposition only
          common_reference_t<const iter_value_t<I>&&, iter_rvalue_reference_t<I>&&>;