What is the preferred way to expose STL-style user iteration?

(also see Is there a good way not to write all twelve required container functions for a custom type in C ++? )


For a class like

namespace JDanielSmith { class C { const size_t _size; const std::unique_ptr<int[]> _data; public: C(size_t size) : _size(size), _data(new int[size]) {} inline const int* get() const noexcept { return _data.get(); } inline int* get() noexcept { return _data.get(); } size_t size() const noexcept { return _size; } }; } 

What is the preferred way to set the iteration? Should I write member functions begin() / end() (and cbegin() / cend() )?

 const int* cbegin() const { return get(); } const int* cend() const { return cbegin() + size(); } 

or should they be non-functional functions?

 const int* cbegin(const C& c) { return c.get(); } const int* cend(const C& c) { return cbegin(c) + c.size(); } 

Should begin() / end() overload both const and non- const ?

  const int* begin() const { return get(); } int* begin() { return get(); } 

Could you still think? Are there tools / methods to make this β€œeasy to do right” and reduce the amount of boiler room code?


Some related questions / discussion include:

  • Should custom containers have free start and end functions?
  • Why use start and end functions without participating in C ++ 11?
  • When to use std::begin and std::end instead of containers of specific versions
+7
c ++ iterator stl
source share
4 answers

I propose creating both sets of functions β€” member functions, as well as non-member functions, to provide maximum flexibility.

 namespace JDanielSmith { class C { const size_t _size; const std::unique_ptr<int[]> _data; public: C(size_t size) : _size(size), _data(new int[size]) {} inline const int* get() const { return _data.get(); } inline int* get() { return _data.get(); } size_t size() const { return _size; } int* begin() { return get(); } int* end() { return get() + _size; } const int* begin() const { return get(); } const int* end() const { return get() + _size; } const int* cbegin() const { return get(); } const int* cend() const { return get() + _size; } }; int* begin(C& c) { return c.begin(); } int* end(C& c) { return c.end(); } const int* begin(C const& c) { return c.begin(); } const int* end(C const& c) { return c.end(); } const int* cbegin(C const& c) { return c.begin(); } const int* cend(C const& c) { return c.end(); } } 

Member functions are needed if you want to use objects of type C as arguments to std::begin , std::end , std::cbegin and std::cend .

Non-member functions are necessary if you want to use objects of type C as arguments only for begin , end , cbegin and cend . ADL will ensure that non-member functions are found for such usages.

 int main() { JDanielSmith::C c1(10); { // Non-const member functions are found auto b = std::begin(c1); auto e = std::end(c1); for (int i = 0; b != e; ++b, ++i ) { *b = i*10; } } JDanielSmith::C const& c2 = c1; { // Const member functions are found auto b = std::begin(c2); auto e = std::end(c2); for ( ; b != e; ++b ) { std::cout << *b << std::endl; } } { // Non-member functions with const-objects as argument are found auto b = begin(c2); auto e = end(c2); for ( ; b != e; ++b ) { std::cout << *b << std::endl; } } } 
+1
source share

There is a standard that describes what the interfaces of your class look like if you want them to conform to the STL. C ++ has such a concept of "concepts" that link the requirements for a given class as a sufficient implementation of the concept. It almost became a language feature in C ++ 11.

A concept that might interest you is the Container concept. As you can see, to meet the requirements of the Container concept, you need begin , cbegin , end and cend as member functions (by the way).

Since it looks like you are storing your data in an array, you might also be interested in the SequenceContainer .

+6
source share

I will take option C.

The main problem here is that std::begin() does not actually work for finding the non-term begin() with ADL. So the real solution is to write your own, which does:

 namespace details { using std::begin; template <class C> constexpr auto adl_begin(C& c) noexcept(noexcept(begin(c))) -> decltype(begin(c)) { return begin(c); } } using details::adl_begin; 

Now it doesn't matter if you write begin() as a member function or non-member, just use adl_begin(x) everywhere and it will work. Both for standard containers and for raw arrays. This conveniently takes a step towards discussing participants and non-members.


And yes, you must have const and not const overload begin() and friends if you want to open access to const and not const .

+2
source share

To create a valid iterator, you must make sure that std :: iterator_traits is valid. This means that you must set the iterator category among other things.

The iterator should implement iterator (), iterator (iterator & &), iterator (iterator const &), operator ==, operator! =, operator ++, operator ++ (int), operator *, operator = and operator->. It’s also nice to add the <operator and the + operator if you can (you cannot always, for example, linked lists.)

 template <typename T> class foo { public: using value_type = T; class iterator { public: using value_type = foo::value_type; using iterator_category = std::random_access_iterator_tag; // or whatever type of iterator you have... using pointer = value_type*; using reference = value_type&; using difference_type = std::ptrdiff_t; // ... }; class const_iterator { // ... }; iterator begin() { /*...*/ } iterator end() { /*...*/ } const_iterator cbegin() const { /*...*/ } const_iterator cend() const { /*...*/ } /* ... */ }; 

See: http://en.cppreference.com/w/cpp/iterator/iterator_traits for more information on what you need to create a valid iterator. (Note: you also need certain properties as a valid "container", for example .size ())

Ideally, you should use member functions to start and end, but this is not required ... you can also overload std :: begin and std :: end. If you do not know how to do this, I suggest you use member functions.

You must create begin () const and end () const, but it must be an alias for cbegin (), NEVER the same as begin ()!

-2
source share

All Articles