encode
below is a dumbed down version of a transformation I am working on (run it on godbolt):
#include <array>
#include <iostream>
#include <ranges>
int main() {
static constexpr auto arr = std::array{1, 2, 3, 4, 5, 6, 7};
static constexpr auto encode = std::views::chunk(3) |
std::views::transform([](auto chunk) {
std::array<int, 4> a{};
auto i = 0;
for (const auto j : chunk) {
a.at(i++) = j;
}
a.at(3) = a.at(0);
return a;
}) |
std::views::join;
auto encoded = arr | encode;
static_assert(std::ranges::contiguous_range<decltype(arr)>);
static_assert(std::ranges::input_range<decltype(encoded)>);
static_assert(not std::ranges::forward_range<decltype(encoded)>);
for (const auto i : encoded) {
std::cout << i;
}
std::cout << '\n';
return 0;
}
Output:
123145647007
As the static_assert
s show, although the input is a contiguous_range
, the output is not even a forward_range
. It is not necessarily a problem for me, but as a matter of principle, I would like my adaptors to be as range property-preserving as possible. Is there an obvious way to rewrite encode
to make it preserve forward_range
s or is that type of chunk transformation always going to break that property?
Edit, additional question:
If there is no obvious way, is there a non-obvious way, like one that would involve implementing the range adaptor from scratch?
As it turns out, the answer is yes, there is a way to rewrite encode
to even preserve random_access_range
s and sized_range
s (run it on godbolt):
#include <array>
#include <iostream>
#include <ranges>
struct encode_fn : std::ranges::range_adaptor_closure<encode_fn> {
template <std::ranges::viewable_range R>
requires std::convertible_to<std::ranges::range_reference_t<R>, int>
constexpr auto operator()(R &&r) const {
return std::views::cartesian_product(
std::forward<R>(r) | std::views::chunk(3),
std::views::iota(0, 4)) |
std::views::transform([](auto indexed) -> int {
auto [v, i] = indexed;
if (i == 3) {
return v[0];
}
if (i > std::ranges::size(v) - 1) {
return 0;
}
return v[i];
});
}
};
int main() {
static constexpr auto arr = std::array{1, 2, 3, 4, 5, 6, 7};
static constexpr encode_fn encode{};
auto encoded = arr | encode;
static_assert(std::ranges::contiguous_range<decltype(arr)>);
static_assert(std::ranges::random_access_range<decltype(encoded)>);
static_assert(std::ranges::sized_range<decltype(encoded)>);
static_assert(std::ranges::size(arr | encode) == 12);
for (const auto i : encoded) {
std::cout << i;
}
std::cout << '\n';
return 0;
}
Same output. With the help of cartesian_product
, we handle every chunk multiple times, producing one transformed chunk item at a time, thus removing the need to ever store intermediate chunks.