The initial question was why with the following code,
std::vector<int> coll{1,4,7,10};
auto iseven = [](auto&& i){return i % 2 == 0; };
auto colleven = coll | std::views::filter(iseven);
// first view materialization
for(int& i : colleven)
{
i += 1;
}
for(auto i : coll)
std::cout << i << ' ';
std::cout << std::endl;
// second view materialization
for(int& i : colleven)
{
i += 1;
}
for(auto i : coll)
std::cout << i << ' ';
and by materializing the view twice, we get two different results. This is definately weird at first sight. The output:
1 5 7 11
1 6 7 11
After doing some research and looking into potential duplicates I understand that this is the cause of Undefined Behavior per https://eel.is/c++draft/range.filter#iterator-1.
Basically, std::filter_view::iterator
- and other similar views - caches the begin iterator (filter_view is derived from remove_if_view
) in order to achieve "laziness" resulting in it maintaining internal state. In the specific example the standard dictates that "even after modification of the view element the user should take care that the predicate remains true." So my question now becomes:
Isn't that a weird requirement? Asking from the user not to do something that otherwise would feel natural, materializing a filter
view twice, that is. What are the compromises that we would have to make in order to alleviate this restriction and why didn't we make them?
Note: My question regards standard views and I know the code I linked is from range-v3. I presume the reference implementation corresponds to the standard in this case.