concat_view rejects non-movable references
Hewill Kang

Created on 2024-05-01.00:00:00 last changed 1 month ago


Date: 2024-05-04.17:00:35

Proposed resolution:

This wording is relative to N4981.

  1. Modify [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
                              concat-value-t<Rs...>&> &&
                              concat-rvalue-reference-t<Rs...>&&> &&
                              concat-value-t<Rs...> const&> &&
                                         iterator_t<Rs>> && ...);
Date: 2024-05-01.00:00:00

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.

Date User Action Args
2024-05-04 17:00:35adminsetmessages: + msg14105
2024-05-01 00:00:00admincreate