Reading through the upcoming C++26 changes, I ran into an example that captures a recurring tension in modern C++ very well. // Predicate: select monsters that are currently dead. auto dead = [](const auto& monster) { return monster.isDead(); }; // Problematic pattern: // We iterate over a filtered view and then mutate the same property // that the filter depends on. for (auto& monster : monsters | std::views::filter(dead)) { monster.bringBackToLife(); // Undefined behavior } At first glance, this feels surprising. C++26 proposes this form instead: // Treat the range as input-only before filtering. // This avoids the multi-pass assumptions that make the previous code invalid. auto dead = [](const auto& monster) { return monster.isDead(); }; for (auto& monster : monsters | std::views::as_input | std::views::filter(dead)) { monster.bringBackToLife(); // OK } What makes this interesting is not just the rule itself, but the feeling it creates. When a language needs an extra adapter to make a very intuitive loop valid, it raises a deeper design question: is the programmer doing something exotic, or is the abstraction exposing machinery that leaks too much into everyday code? Examples like this still make C++ feel as if correctness sometimes depends less on what the code means, and more on whether you remembered the exact operational contract of the pipeline. This is one of those moments where modern C++ feels incredibly powerful, but also slightly at odds with how humans naturally expect code to behave. #cpp #cpp26 #programming #softwareengineering #systemsprogramming
The idea of a view is a lightweight read only representation. Plus writing this in a more conservative way is actually more readable and just as performant once a proper compiler reduces it to what's actually happening.
In the end, isn't this simply caused by the fact that C++ has mutable as default instead of constant as default?
"filter" caches predicate results to avoid re-evaluation on multi-pass iteration, which is why mutating the filtered property breaks it. as_input basically says "one pass only, skip the cache." The UB is a deliberate tradeoff, not an oversight imo.