Home > database >  Is it okay to let a range view own a shared_ptr containing state it needs?
Is it okay to let a range view own a shared_ptr containing state it needs?

Time:08-14

Basically I am trying to get around the fact that the following does not work with range-v3 ranges: (this is a toy example but illustrates the problem.)

namespace rv = ranges::views;

auto bad_foo() {
    std::vector<int> local_vector = { 23, 45, 182, 3, 5, 16, 1 };
    return local_vector |
        rv::transform([](int n) {return 2 * n; });
}

int main() {

    for (int n :bad_foo()) {
        std::cout << n << " ";
    }
    std::cout << "\n";
    return 0;
}

The above doesn't work because we are returning a view that references data that will go out of scope when bad_foo returns.

What I want to do is store the vector in a shared pointer in such a way that the range view will keep the shared pointer alive. Note that the obvious version of this idea does not work i.e.

auto still_bad_foo() {
    auto shared_vector = std::make_shared<std::vector<int>>(
        std::initializer_list{ 23, 45, 182, 3, 5, 16, 1 }
    );
    return *shared_vector |
        rv::transform([](int n) {return 2 * n; });
}

still fails because shared_vector is not actually getting captured by the range view. What we want to do is force the range view to own the shared pointer.

The following seems to work to me.

auto good_foo() {
    auto shared_vector = std::make_shared<std::vector<int>>(
        std::initializer_list{ 23, 45, 182, 3, 5, 16, 1 }
    );
    return rv::single(shared_vector) |
        rv::transform([](auto ptr) {  return rv::all(*ptr); }) |
        rv::join |
        rv::transform([](int n) {return 2 * n; });
}

We use single to turn the shared pointer into a single item range view, turn the single item into a range of ints by dereferencing the pointer in transform and wrapping the vector in a range with all, yielding a composition of range views, which we then flatten via join.

My question is is the above really safe and if so is there a less verbose version of the same idea?

Edit: updated the question to just be about range-v3 ranges as @康桓瑋 has alerted me that this issue is not a problem with C 20 ranges as long as it is not a requirement for the owning range views to be copyable.

CodePudding user response:

Basically I am trying to get around the fact that the following does not work: (this is a toy example but illustrates the problem. I'm using range-v3 but this question applies to standard ranges too)

If you are using the standard <ranges> then you don't have to worry about this, just move the vector into the pipe, which will move the ownership of the vector into owning_view

auto not_bad_foo() {
  std::vector<int> local_vector = { 23, 45, 182, 3, 5, 16, 1 };
  return std::move(local_vector) | // <- here
    std::views::transform([](int n) {return 2 * n; });
}

Demo

CodePudding user response:

How about capturing the vector in a lambda to extend its lifetime?

auto create_foo() {
    std::vector<int> local_vector = { 23, 45, 182, 3, 5, 16, 1 };
    return [captured_vector = std::move(local_vector)]() {
        return captured_vector | rv::transform([](int n) {return 2 * n; });
    };
}

int main() {
    for (int n : create_foo()()) {
        std::cout << n << " ";
    }
    std::cout << "\n";
}
  • Related