Is there a move-or-copy equivalent of std::forward for perfect forwarding in std::ranges?
Asked Answered
G

3

5

How can I combine the following two functions into one? Is there something similar to std::forward, but for ranges?

#include <ranges>
#include <vector>
#include <algorithm>

template<class RangeIn, class RangeOut>
void moveOrCopy(RangeIn& from, RangeOut& to)
{
    std::ranges::copy(from, std::back_inserter(to));
}

template<class RangeIn, class RangeOut>
    requires std::is_rvalue_reference_v<RangeIn&&>
void moveOrCopy(RangeIn&& from, RangeOut& to)
{
    std::ranges::move(from, std::back_inserter(to));
}

void test()
{
  std::vector<int> a, b;
  moveOrCopy(a, b); // copy
  moveOrCopy(std::move(a), b); // move
}

There is std::ranges::forward_range, but that's related to forward_iterator, not perfect forwarding.


Handy tool with the above code: https://cppinsights.io/s/45c86608

Intuitive reference for C++ references: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

Gregggreggory answered 22/1 at 5:49 Comment(5)
This doesn't look like a useful function to me. a is a range, but so is std::ranges::views::all(a). You would want to move those under totally different conditions.Freemasonry
The existing solution doesn't seem to be reasonable for general ranges anyway. The identity between "the range is an rvalue" and "the actual elements of the range are temporaries" holds for std::vector, but not for views, for instance. @n.m.couldbeanAI's comment probably is referring to the same thing.Mushy
You might want to check !borrowed_range<RangeIn> instead: godbolt.org/z/TEhdro968Mournful
@n.m.couldbeanAI very good point, thanks! Could something like owning_view be used to make this method safer?Gregggreggory
I think it should work with owning_view but I haven't tried itFreemasonry
F
4

It is a bad idea to infer the lifetime property of the elements from a generic range's value category. For example:

  1. A std::span<std::string> should not have its elements moved from, even if the span is an rvalue; the value category of the span is completely irrelevant.
  2. Likewise for a ranges::transform_view<std::span<std::string>, some_function> rvalue, even though it is not a borrowed_range.
  3. Likewise for a boost::iterator_range<std::string*>, even though this legacy type has not declared itself to be a view;
  4. Likewise for an owning_view<boost::iterator_range<std::string*>>, even though it's a specialization of owning_view.

There's currently no concept or trait that would allow one to reliably detect when it's safe to do this. The best that can be done is to have the caller explicitly opt-in using something like views::as_rvalue in C++23.

Forceps answered 25/1 at 6:6 Comment(0)
B
7

If you have C++23, you can use std::ranges::transform with std::forward_like.

template<class RangeIn, class RangeOut>
void moveOrCopy2(RangeIn&& from, RangeOut& to)
{
    std::ranges::transform(from, std::back_inserter(to), [](auto&& arg) -> decltype(auto) {
        return std::forward_like<RangeIn>(arg);
    });
}

Demo: https://godbolt.org/z/P81of9qav

Biochemistry answered 22/1 at 7:0 Comment(0)
Z
6

It is not overly clever but if constexpr can clean it up a little bit:

#include <ranges>
#include <vector>
#include <algorithm>

template<class RangeIn, class RangeOut>
void forward_range(RangeIn&& from, RangeOut& to)
{
    if constexpr(std::is_rvalue_reference_v<RangeIn&&>)
        std::ranges::move(from, std::back_inserter(to));
    else
        std::ranges::copy(from, std::back_inserter(to));
}

void test()
{
  std::vector<int> a, b;
  forward_range(a, b); // copy
  forward_range(std::move(a), b); // move
}
Zsazsa answered 22/1 at 6:10 Comment(0)
F
4

It is a bad idea to infer the lifetime property of the elements from a generic range's value category. For example:

  1. A std::span<std::string> should not have its elements moved from, even if the span is an rvalue; the value category of the span is completely irrelevant.
  2. Likewise for a ranges::transform_view<std::span<std::string>, some_function> rvalue, even though it is not a borrowed_range.
  3. Likewise for a boost::iterator_range<std::string*>, even though this legacy type has not declared itself to be a view;
  4. Likewise for an owning_view<boost::iterator_range<std::string*>>, even though it's a specialization of owning_view.

There's currently no concept or trait that would allow one to reliably detect when it's safe to do this. The best that can be done is to have the caller explicitly opt-in using something like views::as_rvalue in C++23.

Forceps answered 25/1 at 6:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.