I wanted to view a 3x3 grid column by column, so I figured I would use std::views::stride
like so:
#include <array>
#include <iostream>
#include <ranges>
auto main() -> int {
auto grid = std::array<std::array<int, 3>, 3>{{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}};
namespace vs = std::views;
auto strideCount = grid.size();
auto allElements = grid | vs::join;
auto columns = vs::iota(0uz, strideCount)
| vs::transform([allElements, strideCount](auto n) {
return allElements
| vs::drop(n)
| vs::stride(strideCount);
});
for (auto&& column : columns) {
for (auto element : column) {
std::cout << element << ' ';
}
std::cout << '\n';
}
}
This works and prints:
1 4 7
2 5 8
3 6 9
That is, it prints every element column by column.
In my original example I wanted to test whether each column satisfies a given criteria. Instead of a range-based for ()
loop, I tried using std::ranges::all_of()
:
#include <algorithm>
#include <array>
#include <iostream>
#include <ranges>
auto main() -> int {
auto grid = std::array<std::array<int, 3>, 3>{{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}};
namespace vs = std::views;
auto strideCount = grid.size();
auto allElements = grid | vs::join;
auto columns = vs::iota(0uz, strideCount)
| vs::transform([allElements, strideCount](auto n) {
return allElements
| vs::drop(n)
| vs::stride(strideCount);
});
std::ranges::all_of(columns, [](auto&& column) {
for (auto element : column) {
// logic that alters whether we return true or false
}
return true;
});
}
The only difference is that instead of:
for (auto&& column : columns) {
for (auto element : column) {
std::cout << element << ' ';
}
std::cout << '\n';
}
I now have:
std::ranges::all_of(columns, [](auto&& column) {
for (auto element : column) {
// logic that alters whether we return true or false
}
return true;
});
With the appropriate #include <algorithm>
.
However, this unfortunately does not work. It fails here (in the std::ranges::all_of()
snippet):
for (auto element : column) {
With error claiming that:
error: passing 'const std::ranges::stride_view<std::ranges::drop_view<std::ranges::join_view<std::ranges::ref_view<std::array<std::array<int, 3>, 3> > > > >' as 'this' argument discards qualifiers [-fpermissive]
For both begin()
and end()
.
However, if I instead of taking auto&& column
take auto column
in the lambda, the code compiles.
Why is it so? Why can I use forwarding reference for each column in the for ()
loop example and can't do that in the lambda argument in std::ranges::algorithm
cases?
I am using gcc version 13.1.0 (MinGW-W64 x86_64-msvcrt-mcf-seh, built by Brecht Sanders)
(output of g++ -v
).
This is LWG 3996, for which P2997 specifically provides proposed wording.
ranges::all_of
requires the predicate Pred
to satisfy indirect_unary_predicate<Pred, projected<I, Proj>>
, which ultimately leads to
invocable<Pred&, iter_common_reference_t<projected<I, Proj>>>
must be satisfied, where Proj
is the projection function.
Class template std::projected
is a helper type used to construct a new iterator type whose reference type is the result of applying Proj
(std::identity
in most cases) to the iter_reference_t<I>
, which facilitates the spelling of the requirements of the "rangified" algorithm ([projected]):
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 = projected-impl<I, Proj>::type;
}
Let's go back to invocable<Pred&, iter_common_reference_t<projected<I, Proj>>>
.
In your example, the original iterator I
's reference
and value_type
are both prvalue stride_view
, so its common reference iter_common_reference_t<I>
is also stride_view
.
However, projected<I, Proj>
's reference
and value_type
are stride_view&&
and stride_view
respectively, which makes the result of iter_common_reference_t<projected<I, Proj>>
end up being calculated as const stride_view&
. Since stride_view
is not const
-iterable, this results in a hard error during instantiation because we call const stride_view
's begin
inside an unconstrained lambda.
This is a standard defect. It seems that we still need the proposed resolution of LWG 3859 to make projected<I, std::identity>
just I
, although it is marked as "Resolved by P2609R3", which is not the case in this case.
The workaround is to use a dummy identity
that returns prvalue so that the common reference of the projected iterator is still a prvalue (demo):
std::ranges::all_of(columns, [](auto&& column) {
for (auto element : column) {
// logic that alters whether we return true or false
}
return true;
}, [](auto r) { return r; });