0

I would like to design a custom container, by building on top of std::vector. Instead of extending it, I decided to use composition, and create a private std::vector member of my class.

I would like to implement iterators as well. How is it possible to satisfy the requirements of RandomAccessIterator this way? I was able to find LegacyContiguousIterator, but maybe I should avoid it, since it's legacy.

Iter Ator
  • 8,226
  • 20
  • 73
  • 164
  • 1
    You should use https://en.cppreference.com/w/cpp/iterator/random_access_iterator rather than the version from the TS – Alan Birtles Dec 26 '21 at 09:50
  • Check the operations required by [`std::random_access_iterator`](https://en.cppreference.com/w/cpp/iterator/random_access_iterator) and implement them one by one. – 康桓瑋 Dec 26 '21 at 09:52
  • There are two sets of iterator concepts. The `Legacy...Iterator` (described in English), and `std::..._iteraator` (actual C++20 `concept`s). To be on the safe side, you should follow both. – HolyBlackCat Dec 26 '21 at 09:58
  • What do you want the iterators for your custom container to achieve that cannot be achieved using the iterators from the contained `std::vector`? – Peter Dec 26 '21 at 10:04
  • @Peter I have to refactor a legacy system, that uses custom containers. I would like to rewrite those containers using the ones from the standard library. As far as I know, it is not recommended to extend them, so I will use them as private members – Iter Ator Dec 26 '21 at 10:06
  • 2
    @IterAtor - If you use a `std::vector` as the storage, you already have `std::vector::iterator` that iterates over the storage. So the first attempt would be to reuse that iterator, Does that not work? – BoP Dec 26 '21 at 10:14
  • @BoP No, because I have to provide the same interface as the old class, if I want to replace it, without rewriting all the other code as well. – Iter Ator Dec 26 '21 at 10:20
  • @IterAtor That's not quite what I meant. If you already have a `std::vector` as a member, you already have iterators that iterate over elements of that vector in various ways (since they are random access iterators). You will only need different iterators if you have a functional requirement to achieve something, using iterators, that the vector's iterators do not allow you to achieve. – Peter Dec 26 '21 at 10:23
  • @Peter So you recommend to just return the iterators of the vector instead of wrapping them as well? – Iter Ator Dec 26 '21 at 10:29
  • 1
    @IterAtor You need to decide whether you are preserving your old interface (e.g. by providing iterators that support your old interface and mapping every operation using that interface to the underlying vector's iterators) or implementing iterators that comply with standard concepts. The first does not require implementation of iterators that comply with standard requirements. If you don't have a specific requirement to support the old interface, then you can simply use the vector's iterators directly (and update all code that uses your old interface so it uses vector's iterators). – Peter Dec 26 '21 at 10:40

1 Answers1

0

Your question would be clearer if it contained the code you're working with so far. But, reading between the lines, the simplest solution is probably exactly what Peter says in the comments: Simply have your begin and end methods expose the begin/end iterators of your private vector member.

class Book {};

class Library {
    std::vector<Book> books_;
public:
    auto begin() const { return books_.begin(); }
    auto end() const { return books_.end(); }
};
static_assert(std::ranges::contiguous_range<Library>);
static_assert(std::same_as<std::ranges::iterator_t<Library>,
                           std::vector<Book>::const_iterator>);
~~~
assert(std::ranges::is_sorted(mylibrary, ByAuthor());

Here, my Library is an iterable (and contiguous) range of Book objects. I could make it a mutable range by exposing non-const iteration too:

class Library {
    std::vector<Book> books_;
public:
    auto begin() { return books_.begin(); }
    auto end() { return books_.end(); }
    auto begin() const { return books_.begin(); }
    auto end() const { return books_.end(); }
};

static_assert(std::same_as<std::ranges::iterator_t<Library>,
                           std::vector<Book>::iterator>);
static_assert(std::same_as<std::ranges::iterator_t<const Library>,
                           std::vector<Book>::const_iterator>);
~~~
std::ranges::sort(mylibrary, ByAuthor();

The alternative is to write your own iterator type — a class type that exposes operator*, operator++, operator==, and so on. For a complete working skeleton that you can flesh out with your own contents, see the accepted answer to "In C++20, how do I write a contiguous iterator?" — the answer for random-access iterators will be exactly the same except that you won't provide the element_type member.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159