c++range-v3

Why aren't temporary container objects pipeable in range-v3?


Why is the following

#include <iostream>
#include <string>
#include <range/v3/all.hpp>

std::vector<int> some_ints() {
    return { 1,2,3,4,5 };
}

int main() {
    auto num_strings = some_ints() |
        ranges::views::transform([](int n) {return std::to_string(n); }) |
        ranges::to_vector;
    
    for (auto str : num_strings) {
        std::cout << str << "\n";
    }

    return 0;
}

an error, while

int main() {
    auto ints = some_ints();
    auto num_strings = ints |
        ranges::views::transform([](int n) {return std::to_string(n); }) |
        ranges::to_vector;

    for (auto str : num_strings) {
        std::cout << str << "\n";
    }

    return 0;
}

is fine?

I would expect the lifetime of the temporary to be extended to the lifetime of the whole pipeline expression so I don't understand what the problem is.

The error from Clang is

<source>:10:36: error: overload resolution selected deleted operator '|'
    auto num_strings = some_ints() |
                       ~~~~~~~~~~~ ^
/opt/compiler-explorer/libs/rangesv3/0.11.0/include/range/v3/view/view.hpp:153:13: note: candidate function [with Rng = std::vector<int, std::allocator<int>>, ViewFn = ranges::detail::bind_back_fn_<ranges::views::transform_base_fn, (lambda at <source>:11:34)>] has been explicitly deleted
            operator|(Rng &&, view_closure<ViewFn> const &)    // ****** READ THIS *******

from Visual Studio I get

error C2280: 'std::vector<int,std::allocator<int>> ranges::views::view_closure_base_ns::operator |<std::vector<int,std::allocator<int>>,ranges::detail::bind_back_fn_<ranges::views::transform_base_fn,main::<lambda_1>>>(Rng &&,const ranges::views::view_closure<ranges::detail::bind_back_fn_<ranges::views::transform_base_fn,main::<lambda_1>>> &)': attempting to reference a deleted function
1>        with
1>        [
1>            Rng=std::vector<int,std::allocator<int>>
1>        ]

Both errors seem to be saying that the pipe operator is explicitly deleted for r-value references?


Solution

  • Short answer would be because they are lazy and | does not transfer ownership.

    I would expect the lifetime of the temporary to be extended to the lifetime of the whole pipeline expression so I don't understand what the problem is.

    Yes, that is exactly what would happen, but nothing more. Meaning that as soon as the code hits ;, some_ints() dies and num_strings now contains "dangling" range. So a choice was made to forbid this example to compile.

    To expand on my comment, the issue is actually within operator| which does not take rvalue(~temporary) on its left side and transform on the right. It does not because it cannot know in advance whether the result will be iterated immediately while the temporary is still alive (that is your case) or whether you will just store the range and iterate over it later. For the latter, the iteration would happen with a dangling reference to the now-dead temporary.

    Yes, there might have been some "look-ahead overload magic" to see whether you iterate the range and store it somewhere in a single expression, but it might be brittle or not worth the complexity. The sane decision was to forbid this at compile-time as the runtime errors from this might have been too common and hard to debug.