Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it valid to join a transform view whose elements are temporary containers?

Tags:

c++

std-ranges

Consider this C++23 example:

#include <ranges>
#include <print>
#include <vector>

namespace r = std::ranges;
namespace rv = std::ranges::views;

std::vector<int> consecutive_triple(int n) {
    return { n, n + 1, n + 2 };
}

int main() {

    std::vector<int> numbers = { 0, 10, 20, 30, 40 };
    auto more_numbers = numbers | 
        rv::transform(consecutive_triple) | rv::join;

    // -- Does this help? ...
    //auto more_numbers = numbers | 
    //    rv::transform(consecutive_triple) | rv::join | 
    //    r::to<std::vector>();

    for (auto v : more_numbers) {
        std::print("{} ", v);
    }
    std::println("");

    return 0;

}

Each call to consecutive_triple returns a temporary std::vector<int>, so the inner ranges in the transform view are pure rvalues. When join tries to iterate them, the vectors no longer exist.

I believe this relies on undefined behavior, since the subranges’ lifetimes end before join can safely traverse them.

Questions:

  1. Does the C++23 standard guarantee this is invalid / undefined?

  2. Is there any standard-conforming way to write this without explicitly making a container that stores the intermediate vectors before joining them?

like image 259
jwezorek Avatar asked Oct 29 '25 18:10

jwezorek


1 Answers

Each call to consecutive_triple returns a temporary std::vector<int>, so the inner ranges in the transform view are pure rvalues. When join tries to iterate them, the vectors no longer exist.

Their copies are stored in the join_view's internal data member non-propagating-cache (thanks to P2328), so there are no lifetime issues. The copies are only destroyed when iterating to the next inner range.

In other words, join_view specifically comes with cache_latest-like functionality for pure rvalue inner ranges, as this is a common use case worth supporting.

It should be noted that P2328 allows join_view to support even non-moveable ones, for example:

struct Nonmovable {
  int* begin();
  int* end();
  Nonmovable() = default;
  Nonmovable(Nonmovable&&) = delete;
};

auto r = std::views::iota(0, 5)
       | std::views::transform([](int) -> Nonmovable { return {}; })
       | std::views::join; // ok
like image 75
康桓瑋 Avatar answered Nov 01 '25 08:11

康桓瑋



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!