为何ranges::filter_view可修改而ranges::transform_view不可修改的底层原因
我正在探索C++20 ranges与views,借助cppinsights.io深入研究细节时,发现ranges::filter_view与ranges::transform_view在尝试修改范围元素时表现截然不同。将两段测试代码放入cppinsights.io后,我注意到迭代器(如__begin1、__end1)的类型差异导致了修改行为的不同,这也是transform_view无法修改原元素的原因。我的观察是否正确?能否对此进行详细解释?
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; // 不会修改原数组 } for (const auto& num : numbers) { std::cout << num << " "; } std::cout << '\n'; return 0; }
filter_view测试代码
#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; // 会修改原数组 } for (const auto& num : numbers) { std::cout << num << " "; } std::cout << '\n'; return 0; }
你的观察完全正确,核心差异源于两个视图的迭代器解引用返回值的性质不同:
1. filter_view的可修改本质
filter_view是对原范围的元素筛选,它的迭代器只是原容器迭代器的简单包装。解引用该迭代器时,返回的是原元素的左值引用(比如int&)。循环中auto&& value绑定的是原vector中元素的引用,对value的修改会直接作用于原容器的元素,这就是filter_view代码能修改原数组的原因。
从迭代器类型看,filter_view的迭代器继承了原容器迭代器的可写特性,属于可写迭代器,自然能通过它修改原元素。
2. transform_view的只读特性
transform_view的核心是对每个元素应用转换函数生成新值,默认情况下,它的迭代器解引用返回的是转换函数的返回值(右值)。你的转换函数[](int n) { return n * 2; }返回的是临时int对象,并非原元素的引用。循环中auto&& value绑定的是这个临时对象,修改它只会改变临时值,完全不会影响原vector中的元素。
如果想要让transform_view能修改原元素,你需要让转换函数返回原元素的引用,比如:
auto transformedView = numbers | std::views::transform([](int& n) -> int& { return n *= 2; });
但这已经偏离了transform_view的设计初衷——它本来就是用于生成转换后的值的视图,默认设计就是返回临时值而非原元素引用。
3. 迭代器类型差异的本质
cppinsights.io显示的迭代器类型差异,本质是两种视图的迭代器特性不同:filter_view的迭代器包装了原容器的可写迭代器,解引用直接返回原元素引用;而transform_view的迭代器虽然也包装了原容器迭代器,但解引用时会调用转换函数生成临时值,因此无法通过它修改原元素。
内容的提问来源于stack exchange,提问作者sam

