projected<I, identity>
should just be I
Section: 24.3.6.4 [projected] Status: NAD Submitter: Hewill Kang Opened: 2023-10-12 Last modified: 2024-06-24
Priority: Not Prioritized
View all other issues in [projected].
View all issues with NAD status.
Discussion:
Currently, projected
is a wrapper of the implementation type regardless of whether Proj
is identity
.
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>>>
.
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.
[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."
[St. Louis 2024-06-24 Status changed: Tentatively NAD → NAD.]
Proposed resolution:
This wording is relative to N4958.
Modify 24.3.6.4 [projected] as indicated:
-1- Class template
projected
is used to constrain algorithms that accept callable objects and projections (3.43 [defns.projection]). It combines anindirectly_readable
typeI
and a callable object typeProj
into a newindirectly_readable
type whosereference
type is the result of applyingProj
to theiter_reference_t
ofI
.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>; }