4

Now I can't find any compiler yet that has this support for the "one ranges proposal" so this is more of an academic question. I'm curious if the following will work like I expect

#include <iostream>
#include <vector>
#include <ranges>

auto Foo (){
    std::vector<int> a = {1,2,3,4,5};
    return std::move(a) | std::reverse;
}


int  main(){
   for(auto a : Foo()){
       std::cout << a << std::endl;
   }
}

with an expected output of

5
4
3
2
1

The question has to do with the ownership semantics of the range adaptors. I say I want to move a and then wrap it with a view. What is expected to happen?

  1. Not compile.
  2. Compile but maybe crash with a memory corruption
  3. Work as expected
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • 1
    You can't use views on rvalues, so this will fail at compile time. – Fureeish Aug 28 '19 at 09:57
  • That's a curious limitation. I have my own range based library ( company owned so can't share :( ) but it handles rvalues and lvalues as two seperate cases. lvalues are held using non owning references. rvalues are held by moving the value in and owning it. I was hoping to migrate to std::range at some point. – bradgonesurfing Aug 28 '19 at 10:09
  • 1
    Tough luck, I'm afraid. Can't really point you to the exact wording or reasoning behind it, but I remember that during my brief encounter with Eric Niebler (the author of range-v3), he stated that ["*You can't pipe a temporary container into a view adaptor because it would create dangling references. Save the vector into a named variable first*"](https://stackoverflow.com/questions/57090147/why-cant-i-reverse-a-split-range-using-range-v3?noredirect=1#comment100894827_57090147), which, well, implies that ranges do not work with rvalue sources : – Fureeish Aug 28 '19 at 10:12
  • My trick is that I *always* store the LHS by value rather than reference. But the pipe operator distinguishes rvalues from lvalues. rvalues are moved in. lvalues are wrapped with ``boost::make_iterator_range`` which itself is stored by value. Essentially a ref wrapper. – bradgonesurfing Aug 28 '19 at 10:21
  • This gives an idea of how I did it. https://gist.github.com/bradphelan/80fc63e7051a6bd71c15a6743400f02a – bradgonesurfing Aug 28 '19 at 10:25

2 Answers2

6

Can you return range based views from functions in c++2a?

You can.

But returning view to a local variable or temporary would be useless since behaviour of accessing through the view to the destroyed object would be undefined. Same as returning an iterator, pointer or a reference.

I say I want to move a and then wrap it with a view. What is expected to happen?

At least going by what ranges-v3 does, it static asserts that the operand is an lvalue, which fails the compilation.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Would there be a performance penalty to range-v3 if instead of views storing by reference they always stored by value. It would then be up to the pipe operator to wrap lvalues in a reference wrapper. Would the indirection hurt performance? – bradgonesurfing Aug 28 '19 at 13:12
  • 1
    @bradgonesurfing for sure. Why would a *view* copy something? Forget about performance for a second and focus on the intention. *Views* never own something. – Fureeish Aug 28 '19 at 13:26
  • If you return a view from a function and that view has a source range that is a local variable with the current implementation of range you are not able to return the view. For example the simple trivial example https://wandbox.org/permlink/BGKVWWYfqakSPnHp compiles and then segfaults at runtime. If you try to move the vector into the view it fails to compile. – bradgonesurfing Aug 28 '19 at 13:49
  • 1
    @bradgonesurfing and what does that have to do with that I said? This is an example of returning local objects by reference. Bad idea. We are all aware of it. Views were chosen to be lightweight and cheap to copy - these are the consequences. – Fureeish Aug 28 '19 at 13:50
  • @fureeish You have stated that "views never own something" The question I asked just above was "why". I gave an example of a function returning a view and I am aware that it segfaults and know why. The solution would be to use ``std::move(vi)`` which would tell the operator to move the local variable. Like https://wandbox.org/permlink/g7DsrM7EnSe5zKNg but this does not compile. My question is ``why is this not supported`` It is a usecase that is useful. – bradgonesurfing Aug 28 '19 at 13:54
  • 1
    @bradgonesurfing you said that *The question I asked just above was "why"*. And I provided an answer - "*Views were chosen to be lightweight and cheap to copy - these are the consequences*". Regarding your second question - `why is this not supported`, you have to ask the C++ comittee, not me :> – Fureeish Aug 28 '19 at 13:56
5

Views in range-v3/C++20 ranges are non-owning by design. reverse is always going to be non-owning, so returning that directly to a local range is going to dangle. There's no "owning view" concept in the library.

You can do it by hand though, by writing a custom type that has both the container and the view as members:

auto Foo() {
    std::vector<int> a = {1, 2, 3, 4, 5};

    struct X {
        std::vector<int> a;
        decltype(a | views::reverse) view = a | views::reverse;

        // plus copy/move ctor/assignment

        auto begin() { return view.begin(); }
        auto end()   { return view.end(); }
    };
    return X{std::move(a)};
}

This could probably be generalized.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    The defining characteristic of "view" is...actually I'm not sure what it is nowadays - constant-time destruction? But the laziness of views does come with a cost, so if you need to gobble up the underlying container anyway, maybe views is the wrong choice and you want actions instead. – T.C. Aug 28 '19 at 15:54
  • 3
    Copying that type is dangerous though. – Deduplicator Aug 28 '19 at 16:50
  • You would want a move constructor so that when this is in a pipeline the vector is not copied. – bradgonesurfing Aug 29 '19 at 10:27
  • @bradgonesurfing Yes, Deduplicator already made this comment and I didn't feel like writing them out so I just left a comment. – Barry Aug 29 '19 at 12:43
  • I'm going to mark this as the solution because it looks like the work around required to support my use case. It would be nice if the solution was fleshed out a bit and robustness issues checked. I might drive by later and do that if you don't mind. I'm thinking of something now like ``return std::move(v) | views::own() | views::reverse()`` – bradgonesurfing Aug 29 '19 at 12:50