4027. possibly-const-range should prefer returning const R&

Section: 26.2 [ranges.syn] Status: New Submitter: Hewill Kang Opened: 2023-12-17 Last modified: 2023-12-22 11:29:13 UTC

Priority: Not Prioritized

View other active issues in [ranges.syn].

View all other issues in [ranges.syn].

View all issues with New status.


possibly-const-range currently only returns const R& when R does not satisfy constant_range and const R satisfies constant_range.

Although it's not clear why we need the former condition, this does diverge from the legacy std::cbegin (demo):

#include <ranges>

int main() {
  auto r = std::views::single(0)
        | std::views::transform([](int) { return 0; });
  using C1 = decltype(std::ranges::cbegin(r));
  using C2 = decltype(std::cbegin(r));
  static_assert(std::same_as<C1, C2>); // failed

Since R itself is constant_range, so possibly-const-range, above just returns R& and C1 is transform_view::iterator<false>; std::cbegin specifies to return as_const(r).begin(), which makes that C2 is transform_view::iterator<true> which is different from C1.

I believe const R& should always be returned if it's a range, regardless of whether const R or R is a constant_range, just as fmt-maybe-const in format ranges always prefers const R over R.

Although it is theoretically possible for R to satisfy constant_range and that const R is a mutable range, such nonsense range type should not be of interest.

This relaxation of constraints allows for maximum consistency with std::cbegin, and in some cases can preserve constness to the greatest extent (demo):

#include <ranges>

int main() {
  auto r = std::views::single(0) | std::views::lazy_split(0);
  (*std::ranges::cbegin(r)).front() = 42; // ok
  (*std::cbegin(r)).front() = 42; // not ok

Above, *std::ranges::cbegin returns a range of type const lazy_split_view::outer-iterator<false>::value_type, which does not satisfy constant_range because its reference type is int&.

However, *std::cbegin(r) returns lazy_split_view::outer-iterator<true>::value_type whose reference type is const int& and satisfies constant_range.

Proposed resolution:

This wording is relative to N4971.

  1. Modify 26.2 [ranges.syn], header <ranges> synopsis, as indicated:

    #include <compare>              // see 17.11.1 [compare.syn]
    #include <initializer_list>     // see 17.10.2 [initializer.list.syn]
    #include <iterator>             // see 25.2 [iterator.synopsis]
    namespace std::ranges {
      // 26.7.21 [range.as.const], as const view
      template<input_range R>
        constexpr auto& possibly-const-range(R& r) noexcept { // exposition only
          if constexpr (inputconstant_range<const R> && !constant_range<R>) {
            return const_cast<const R&>(r);
          } else {
            return r;