I've been exploring C++20 ranges and views and used the cppinsights.io website to dig into the details. I noticed something interesting about how filter_view
and transform_view
behave differently when trying to modify the elements in a range.
Now if you paste the code below into the cppinsights.io website, you'll notice that the type of iterators (__begin1, __end1)
causes different modification behavior and the reason why transform_view
is read-only. Is my observation correct? Can someone explain it?"
//For transform_view:
#include <algorithm>
#include <iostream>
#include <list>
#include <ranges>
#include <vector>
int main() {
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
auto transformedView = numbers | std::views::transform([](int n) { return n * 2; });
for (auto&& value : transformedView) {
value += 1; // won't modify
}
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << '\n';
return 0;
}
//for filter
#include <algorithm>
#include <iostream>
#include <list>
#include <ranges>
#include <vector>
int main() {
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
auto filterView = numbers | std::views::filter([](int n) { return n%2== 0; });
for (auto&& value : filterView) {
value += 1; // modify
}
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << '\n';
return 0;
}
A transform view returns the result of a function. In your case, you are returning the result of n * 2
. This is prvalue int
, with no association to the int
s stored in numbers
. Of course modifying this has no effect on numbers
.
If you have a transform
that returns a mutable reference, then you can modify through it.
struct number {
number(int value): value(value) {}
int value;
};
int main() {
std::vector<number> numbers = { 1, 2, 3, 4, 5 };
auto transformedView = numbers | std::views::transform([](number& n) -> int& { return n.value; });
for (auto&& value : transformedView) {
value += 1; // does modify
}
for (const auto& num : numbers) {
std::cout << num.value << " ";
}
std::cout << '\n';
return 0;
}