[libcxx][ranges] std::views::transform invoke the lambda everytime dereferencing

Hi all,

Recently I am starting to use library, and I met an unexpected behavior of my program, after debugging I found it was caused by std::views::transform, when it along with other std::views::*, the lambda of transform will get invoked every time the latter view iterators get dereferenced. For explanation, here is an example:

#include <print>
#include <ranges>

auto main() -> int {
  auto v = std::views::iota(0, 3) | 
           std::views::transform([](auto const &i) {
             std::print("Lambda 1 [{}] --> ", i);
             return i;
           }) |
           std::views::filter([](auto const &_i) {
             std::print("Lambda 2 --> ");
             return true;
           }) |
           std::views::transform([](auto const &i) {
             std::print("Lambda 3 --> ");
             return i; });
  for (auto const &i : v) {
    std::println("{}", i);
  }
  return 0;
}

In my understanding, the lambda 1 should only be called once every round of elements process, but the actual output is:

Lambda 1 [0] --> Lambda 2 --> Lambda 1 [0] --> Lambda 3 --> 0
Lambda 1 [1] --> Lambda 2 --> Lambda 1 [1] --> Lambda 3 --> 1
Lambda 1 [2] --> Lambda 2 --> Lambda 1 [2] --> Lambda 3 --> 2

I admit this is a little bit counter-intuitive for me. I have thought there would be some internal ‘cache’ inside of the views::transform to avoid repetitive invocation, but I also know this would incur space waste (maybe it is more reasonable just for forward_iterator?). So I am wondering what the expected behavior is from pros/standard committees’ perspective, and how to write right program in line with programmer’s expectation using std::ranges. I am all ears.

Thank you.

This is a “known issue” in the C++23 standard. See, e.g., the cache_last view discussed in A Plan for C++26 Ranges for discussion on how to solve this for C++26