4

I'm trying the range interface of c++20, and I added a constructor that takes a range in my container-like type.

class element {
  ...
};

class myclass {
public:
  template <typename Iter>
  myclass(Iter first, Iter last)
    : els(first, last)
  { }

  template <typename Range>
  myclass(Range&& r);

private:
  std::vector<element> els;
};

The iterator-pair version is quite easy. In els_(first, last);, it copies the elements if Iter is a normal iterator, it moves the elements if Iter is a movable iterator such as std::move_iterator<>. It's callers responsibility to explicitly give the movable iterators if the caller wants the elements moved.

In the range version, however, although I can check if the range itself is given in a rvalue reference or lvalue reference, it doesn't help checking if the element can be moved or not.

Let's say that we have a range maker, make_range(), which takes a container and returns a proxy instance which complies the range concept. In the following code, Range of the constructor is an rvalue reference in both cases but obviously the elements should not be moved in the second case.

std::list<element> list_of_elements{...};
myclass c(std::move(list_of_elements)); // should be moved

std::list<element> list_of_elements_to_be_reused{...};
myclass c(make_range(list_of_elements_to_be_reused)); // should not be moved

How can I check if the given range is for copying for moving?

Inbae Jeong
  • 4,053
  • 25
  • 38

1 Answers1

3

You don't. You trust the iterator returned by std::ranges::begin, same as you would trust the iterator Iter to do the right thing. Your second constructor can just delegate:

template <std::ranges::Range Range> // Constraints checked with the library concept
myclass(Range&& r) : myclass(std::ranges::begin(std::move(r)), std::ranges::end(std::move(r)))
{}

The default behavior is to copy, which is sensible. But since std::ranges::begin is a customization point object, it can pick up on user defined begin overloads by ADL. For a user defined type, this setup:

namespace myns {
    class myclass { /* ... */ };
    auto begin(std::vector<myclass>&& v) { return std::make_move_iterator(v.begin()); }
    auto end(std::vector<myclass>&& v) { /* ... * }
}

Will make std::ranges::begin call myns::begin when the class constructor is passed an rvalue vector. It is the user defined type that controls the behavior, which is good.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Will `myclass c(std::vector{...});` copy the elements? or move them? – Inbae Jeong May 12 '19 at 06:20
  • @InbaeJeong - By default it copies. And it's not entirely unreasonable. The vector may be trivially movable, but it doesn't automatically translate to the element types. On the other hand, since `std::ranges::begin` is a customization point object, you can change that. A moment. – StoryTeller - Unslander Monica May 12 '19 at 06:24
  • `myns::begin` might work but it still needs to be provided for all the possible types of containers, since it can't be templatized because of the same reason with my post. Then I see no reason to use ranges. – Inbae Jeong May 12 '19 at 07:14
  • @inbae - Who said it can't be a template? It can. Just needs to be made a better match in partial ordering compared to the catchall deleted overload provided by the standard library. That's not impossible. – StoryTeller - Unslander Monica May 12 '19 at 07:17