4091. concat_view rejects non-movable references

Section: 25.7.18.2 [range.concat.view] Status: New Submitter: Hewill Kang Opened: 2024-05-01 Last modified: 2024-06-24

Priority: 4

View other active issues in [range.concat.view].

View all other issues in [range.concat.view].

View all issues with New status.

Discussion:

In order to prevent non-equality-preserving behavior of operator* and iter_move, concat_view introduces the concat-indirectly-readable concept, part of which is:

template<class Ref, class RRef, class It>
  concept concat-indirectly-readable-impl =                         // exposition only
    requires (const It it) {
      { *it } -> convertible_to<Ref>;
      { ranges::iter_move(it) } -> convertible_to<RRef>;
    };

This isn't quite right because convertible_to checks is_convertible_v which doesn't understand copy elision. This makes the current concat_view unable to work with ranges whose reference is non-movable prvalue:

auto r = std::views::iota(0, 5)
       | std::views::transform([](int) { return NonMovable{}; });
auto c1 = std::ranges::concat_view(r);    // ill-formed, concat_indirectly_readable not satisfied
auto c2 = std::ranges::concat_view(r, r); // ditto

Since std::visit<R> is used in the implementation to perform reference conversion for the underlying iterator, the more accurate one should be is_invocable_r which does understand guaranteed elision.

Note that join_with_view has the same issue because compatible-joinable-ranges requires that the value_type of the inner range and pattern range must satisfy common_with, which always fails for non-movable types. However, this can be automatically resolved by LWG 4074's resolution.

[2024-06-24; Reflector poll]

Set priority to 4 after reflector poll. "Proposed resolution loses the existing requirement that the conversion is equality-preserving." "Don't care about rejecting non-movable reference types."

Proposed resolution:

This wording is relative to N4981.

  1. Modify 25.7.18.2 [range.concat.view] as indicated:

    -1- The exposition-only concat-indirectly-readable concept is equivalent to:

    template<class Ref, class CRef>
    concept concat-ref-compatible-with = is_invocable_r_v<CRef, Ref()>;  // exposition only
      
    template<class Ref, class RRef, class It>
    concept concat-indirectly-readable-impl =                         // exposition only
      requires (const It it) {
        { *it } -> convertible_toconcat-ref-compatible-with<Ref>;
        { ranges::iter_move(it) } -> convertible_toconcat-ref-compatible-with<RRef>;
      };
    
    template<class... Rs>
      concept concat-indirectly-readable =                              // exposition only
        common_reference_with<concat-reference-t<Rs...>&&,
                              concat-value-t<Rs...>&> &&
        common_reference_with<concat-reference-t<Rs...>&&,
                              concat-rvalue-reference-t<Rs...>&&> &&
        common_reference_with<concat-rvalue-reference-t<Rs...>&&,
                              concat-value-t<Rs...> const&> &&
        (concat-indirectly-readable-impl<concat-reference-t<Rs...>,
                                         concat-rvalue-reference-t<Rs...>,
                                         iterator_t<Rs>> && ...);