Title
projected<I, identity> should just be I
Status
nad
Section
[projected]
Submitter
Hewill Kang

Created on 2023-10-12.00:00:00 last changed 4 months ago

Messages

Date: 2024-06-24.17:35:19

Proposed resolution:

This wording is relative to N4958.

  1. Modify [projected] as indicated:

    -1- Class template projected is used to constrain algorithms that accept callable objects and projections ([defns.projection]). It combines an indirectly_readable type I and a callable object type Proj into a new indirectly_readable type whose reference type is the result of applying Proj to the iter_reference_t of I.

    namespace std {
      template<class I, class Proj>
      struct projected-impl {                               // exposition only
        struct type {                                       // exposition only
          using value_type = remove_cvref_t<indirect_result_t<Proj&, I>>;
          using difference_type = iter_difference_t<I>;     // present only if I
                                                            // models weakly_incrementable
          indirect_result_t<Proj&, I> operator*() const;    // not defined
        };
      };
    
      template<indirectly_readable I, indirectly_regular_unary_invocable<I> Proj>
        using projected = conditional_t<is_same_v<Proj, identity>, I, typename projected-impl<I, Proj>::type>;
    }
    
Date: 2024-06-24.17:35:19

[ St. Louis 2024-06-24 Status changed: Tentatively NAD → NAD. ]

Date: 2023-11-15.00:00:00

[ 2023-11-03; Reflector poll ]

NAD. P2997 solves this, and more. "Applying the projection does in fact materialize prvalues, so this is just lying unless we special-case identity everywhere."

Date: 2023-10-14.16:22:27

Currently, projected is a wrapper of the implementation type regardless of whether Proj is identity.

Since identity always returns a reference, this prevents projected<I, identity> from fully emulating the properties of the original iterator when its reference is a prvalue.

Such non-equivalence may lead to unexpected behavior in some cases (demo):

#include <algorithm>
#include <ranges>
#include <iostream>

int main() {
  auto outer = std::views::iota(0, 5)
             | std::views::transform([](int i) {
                 return std::views::single(i) | std::views::filter([](int) { return true; });
               });
  
  for (auto&& inner : outer)
    for (auto&& elem : inner)
      std::cout << elem << " "; // 0 1 2 3 4 
  
  std::ranges::for_each(
    outer,
    [](auto&& inner) {
      // error: passing 'const filter_view' as 'this' argument discards qualifiers
      for (auto&& elem : inner)
        std::cout << elem << " ";
    });
}

In the above example, ranges::for_each requires indirect_unary_predicate<Pred, projected<I, identity>> which ultimately requires invocable<Pred&, iter_common_reference_t<projected<I, identity>>>.

According to the current wording, the reference and indirect value type of projected<I, identity> are filter_view&& and filter_view& respectively, which causes its common reference to be eventually calculated as const filter_view&. Since the former is not const-iterable, this results in a hard error during instantiation because const begin is called unexpectedly in an unconstrained lambda.

It seems like having projected<I, identity> just be I is a more appropriate choice, which makes the concept checking really specific to I rather than a potentially incomplete iterator wrapper.

History
Date User Action Args
2024-06-24 17:35:19adminsetmessages: + msg14210
2023-11-03 18:08:28adminsetmessages: + msg13809
2023-11-03 18:08:28adminsetstatus: new -> nad
2023-10-14 16:19:24adminsetmessages: + msg13757
2023-10-12 00:00:00admincreate