7

Assume that I have a function template that can accept a range of some type T. I want to check whether it's safe to move from that range.

For example, if a function accepts an rvalue reference to a certain value, it is safe to move from it. If, for example, that function may accept both rvalue and lvalue references, we can use std::forward to conditionally move it if necessary.

Now I want to achieve the same thing, but for ranges. Initially I tried using the following concept:

template <typename Range>
concept movable_source = std::ranges::range<Range> &&
        !std::ranges::borrowed_range<Range> &&
        (!std::is_lvalue_reference_v<Range>);

and it worked okay. It reported true for std::vector<T>&&, false for std::vector<T>& and true again for std::vector<T>.

Usage:

template <typename T>
auto print_movability(T&& t) -> void {
    std::cout << movable_source<T> << ' ';
}

auto main() -> int {
    auto vec = std::vector<int>();
    print_movability(vec);
    print_movability(std::move(vec));
    print_movability(std::vector<int>());
}

This correctly prints 0 1 1.

However, the following snippet breaks this logic:

print_movability(vec | std::views::transform([](int& e) -> int& { return e; }));

This reports 1 since the resulting view is neither a borrowed range nor is it an lvalue reference.

I could of course simply add another check to make sure that the type isn't a view, but would that be enough? What would be the correct way to test whether a type is a range whose elements we can move?

Fureeish
  • 12,533
  • 4
  • 32
  • 62
  • 1
    Similar question: [check if elements of a range can be moved?](https://stackoverflow.com/questions/56096579/check-if-elements-of-a-range-can-be-moved) and [How to check whether elements of a range should be moved?](https://stackoverflow.com/questions/67650579/how-to-check-whether-elements-of-a-range-should-be-moved) – zjyhjqs Jun 24 '22 at 14:07

1 Answers1

2

I would suggest making it up to the caller to tell you this by using a range adaptor that produces rvalue references (views::move in range-v3, views::as_rvalue proposed for C++23). That way you don't have to guess, since you'll get a range of rvalues:

algo(vec); // can't move
algo(vec | views::move); // can move
algo(vec | views::transform(f)); // can't move
algo(vec | views::transform(f) | views::move); // can move

Calling this "can move" isn't really meaningful, since we actually get is a range of rvalues, so of course you can move. That's just what you get.


As to guessing... that's pretty hard. Borrowed-ness isn't relevant, so we can skip that part. If the range is an lvalue, pretty sure we can't move. If the range is an rvalue but is a view, we probably can't move (we'll typically get views as rvalues) except for some specific views that we can. So an approximation could be:

template <class R>
inline constexpr bool move_safe_range = !view<R>;

template <class R>
inline constexpr bool move_safe_range<owning_view<R>> = true;

template <class T>
inline constexpr bool move_safe_range<single_view<T>> = true;

template <class R>
concept movable_source =
    input_range<R>
    && (!is_lvalue_reference_v<R>)
    && move_safe_range<R>;

But I'm not sure this is a great approach, cause even an rvalue view could be move-safe, for instance if we're views::transform-ing an owning_view? The views::move/views::as_rvalue is more reliable (even as it requires more annotation).

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you for your answer, Barry. Since you are the expert on those cases (big fan of your proposals), let me shine some more light into the issue itself - I am gathering work and reserach towards potentially introducing a proposal for adding constructors for containers that accept any type of range. For example I would like to do `std::vector(some_int_range)`. I am aware of the proposed `ranges::to`, but I am wondering whether we could add constructors on top of that. Of course if we are being passed a range from which we *should* steal the elements, I want to do so. Any possible advice? – Fureeish Jun 22 '22 at 16:50
  • @Fureeish That proposal does (or, I guess, did, since it's already been adopted) add constructors (e.g. [this one](https://eel.is/c++draft/vector#cons-11) for `vector`) – Barry Jun 22 '22 at 17:00
  • I can see that it uses tag dispatch. I would like to try implementing this feature without it, but that's not the main point. Will that constructor move the elements in this case? `auto d = std::deque{x1, x2, x3}; auto v = std::vector(from_range, std::move(d))?` – Fureeish Jun 22 '22 at 17:02
  • @Fureeish No, it won't. – Barry Jun 22 '22 at 17:08
  • Is it intentional? Was it considered? – Fureeish Jun 22 '22 at 17:14
  • @Fureeish Yes it's intentional. There's not really much to discuss since there's no mechanism by which to ensure that it works. There's no `begin() &&` or anything like that. – Barry Jun 22 '22 at 17:30
  • Thank you. This is a very solid argument for my thesis chapter! – Fureeish Jun 22 '22 at 17:51