When defining my custom containers, it is more easy and very general (for me) making them iterable by just adding size() and []. This is, having the following member methods:
unsigned int size() const { . . . }
T & operator[] (unsigned int pos) { . . . }
In order to benefit from the STL algorithms, I provide an adaptor
from any container class having the above methods to an iterator valid for the STL functions.
With it, I can write easily things like
MyContainer<int, 5> mc;
IteratorFromContainer<MyContainer<int,5>, int> iter (&mc);
int i=1; for (auto & e : iter) { e = i++; }
for (auto e=iter.begin(); e!=iter.end(); ++e) { cout << (*e) << endl; }
int sum = std::accumulate(iter.begin(), iter.end(), 0);
int prod = std::accumulate(iter.begin(), iter.end(), 1, [](int a, int b) {return a*b;});
Surprisingly (to me) my adaptor (template) class works (the above sample code) equally well
with any of the following (1, 2, or 3):
template<typename Container, typename T>
// 1.
class IteratorFromContainer : public std::iterator<input_iterator_tag, T> {
// 2.
class IteratorFromContainer : public std::iterator<output_iterator_tag, T> {
// 3.
class IteratorFromContainer {
Why?. Should not the adaptor derive from std::iterator always?
What kind of iterator (what _tag) should I use, considering that the iterator is based in random access (size() and []) and has output and input capabilities: RandomAccess, ContinguousIterator?
Thanks
C++ uses a thing called duck-typing, which has awesome pros, and some really wierd cons. One of those cons are, if you break the interface contract, it might not notice until much later, if ever. When you use the for-loops, the compiler uses the iterator's copy constructor, operator++(), operator==(...) and operator*(), which you have, so it sometimes may work fine (I'm pretty sure GCC will point out the error in your options #2 & #3 though). I don't know the exact requirements for std::accumulate, but most likely they're similar. And so as long as you have those your code "works".
However, with options #2 and #3, you're breaking the "contract" about what it means to be an iterator. As such, the compiler or library may receive an update, and your code may stop working. The requirements for each iterator type depends on the type of iterator you're modeling, for more details see this page: How to implement an STL-style iterator and avoid common pitfalls?. However, the minimum requirements are you must have these operations:
iterator(const iterator&);
~iterator();
iterator& operator=(const iterator&);
iterator& operator++(); //prefix increment
reference operator*() const;
and also std::iterator_traits<IteratorFromContainer> must have these typedefs:
typedef ???? difference_type; //almost always ptrdif_t
typedef ???? value_type; //almost always T
typedef ???? reference; //almost always T& or const T&
typedef ???? pointer; //almost always T* or const T*
typedef ???? iterator_category; //usually std::forward_iterator_tag or similar
For simplicity, if you don't specialize std::iterator_traits yourself, it will check if your iterator has those typedefs, and if so, it will copy the ones from your iterator. std::iterator has these typedefs, and so if you inherit from it, you automatically have the typedefs, and so std::iterator_traits will automatically have them, so you'll meet the contractual guarantees. But inheriting from std::iterator is not necessary, you can add the typedefs yourself, or specialize iterator_traits.
Since you're working with operator[], it makes sense for you to use std::random_access_iterator_tag, which has many more requirements than those listed above. Again, see my other answer linked above for details on exactly what those members are.
Related
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.
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.
I've read that hiding the actual container type used in a class is a good thing. It makes accidentally (or intentionally) writing code that conforms to the actual container type (that may change and break prior code) harder and thus the code is more robust.
Therefore, that means that this is not a good idea; If the implementation changes from a std::vector to a std::list things will break:
class MyType {
public:
const std::vector<MyType*>& GetChildren() const;
std::vector<MyType*>& GetChildren();
const std::vector<MyType*>::size_type GetChildCount() const;
std::vector<MyType*>::size_type GetChildCount();
private:
MyType* _parent;
std::vector<MyType*> _children;
};
Using a typedef makes it look like it's hidden, but a user only need to look at the mouse-over definition (at least in Visual Studio) to see the actual type and code accordingly. Even worse, the compiler doesn't see the typedef at all, it just sees the underlying type.
class MyType {
public:
typedef std::vector<MyType*> ChildContainer;
typedef ChildContainer::size_type ChildContainerSize;
const ChildContainer& GetChildren() const;
ChildContainer& GetChildren();
const ChildContainerSize GetChildCount() const;
ChildContainerSize GetChildCount();
private:
MyType* _parent;
ChildContainer _children;
};
Question: Is there a way to hide the underlying container type so that it will be difficult for the user to code around it short of wrapping the STL container in a potentially poorly written and not-needed wrapper?
Sometimes you have no choice, but to show the underlying container types. For example, for randomly accessed containers like std::vector v, I can do sort(v.begin(), v.begin()+10); whereas for a std::list li, I can only do li.sort(), how can you hide the container type, and only use iterators? These iterators are totally incompatible. Also, container type is one of the most fundamental consideration in software designing, why should you think about the future possibility of changing container type? That usually means bad designing, and total remake of the software.
You should use typedefs and iterators. Then if you want to change the container, you can because users are only dependent upon the iterators. People can always break it if they want, but you are exposing an easy way to NOT break.
class MyType {
public:
typedef std::vector<MyType*> ChildContainer;
typedef ChildContainer::size_type ChildContainerSize;
typedef std::vector<MyType*> ChildContainerIter;
typedef std::vector<MyType*> ChildContainerCIter;
const ChildContainerCIter GetChildrenBegin() const { return _children.begin(); }
ChildContainerIter GetChildrenBegin() { return _children.begin(); }
const ChildContainerCIter GetChildrenEnd() const { return _children.end(); }
const ChildContainerSize GetChildCount() const;
private:
MyType* _parent;
ChildContainer _children;
};
And no there is not a way to really hide it completely. The compiler needs to know what type it is when their code is compiled.
I'm wondering about one thing. I've got class which has 1 overloaded member function:
class A{
public:
class const_iterator{};
class iterator : public const_iterator{};
iterator find(const K &key);
const_iterator find(const K &key) const;
};
Ad. iterator is inheriting from const_iterator, but it isn't adding anything.
What I want to do is, inside normal find call const find. Something like this:
typename A::iterator A::find(const K &key){
const_iterator it(find(key));
return (*(iterator*)&it);
}
I don't need different implementation of non-const find ATM. Is it possible to do something like this? Because now I'm getting into infinite loop, adding "A::" before find isn't changing anything.
In general, there’s no clean solution for this, unfortunately.
You can call the overloaded find by simply casting this to A const* – but the result will be of the wrong type (const_iterator rather than iterator) and there may not be a conversion between these in the general case (your (*(iterator*)&it) won’t work in your case).
But of course, in your special case, since you defined the two classes, you can define such a conversion by adding an appropriate constructor to iterator:
class iterator : public const_iterator {
iterator(const_iterator const& other) {
// Construct …
}
};
Then you can re-write your non-const find implementation as follows:
A::iterator A::find(const int &key){
const_iterator it(const_cast<A const*>(this)->find(key));
return static_cast<iterator>(it);
}
Incidentally, note the absence of typename in the return type. Since A::iterator isn’t a dependent name, you don’t need (and, at least in C++03, are not allowed to) use typename here.
const_cast<const A*>(this)->find(key);
I have a class that contains, among other things, an std::list. I want to expose this list but only in such a way that the structure and the data it contains are read only, but still can be used with iterators.
The way I've got it 'working' atm is to return a copy of the list. This leave my class 'safe' but of course does nothing to stop the caller from modifying their copy of the list and not getting the right data.
Is there a better way?
Why not return a const std::list& instead?
Instead of exposing the list itself (at all) just expose const_iterators to its beginning and end. See cbegin() and cend() for help in doing this...
Return a const reference:
const std::list<T>& getList() const;
or just return const iterators:
std::list<T>::const_iterator getListBegin() const;
std::list<T>::const_iterator getListEnd() const;
There is a dependency issue in exposing one's data member to the outside world.
If you decide to change your attribute for something better (because list are the last resort container), or because you have a new requirements, then all your clients will be impacted, and that is bad.
One simple alternative is to offer a typedef:
typedef std::list<Foo>::const_iterator const_iterator;
IF your clients use your alias, then it's a simple matter of recompiling the code.
Another alternative is to create your own iterator class (not that difficult) which will embed the actual iterator.
class const_iterator
{
public:
private:
typedef std::list<Foo>::const_iterator base_type;
base_type mBase;
};
You simply forward all the operations to the actual iterator, and your clients (though they will have to recompile if you change your container) cannot accidentally use an unaliased type.
Then, the 3rd solution is similar to the first, except that you abstract the type... it's quite inefficient though (for a list), so I would not really advise it: iterators are supposed to be cheap to copy, you don't want to new anything.
class foo {
private:
typedef std::list<bar> bar_cont_t;
public:
typedef bar_const_t::const_iterator bar_const_iterator;
bar_const_iterator bar_begin() const {return bar_data_.begin();}
bar_const_iterator bar_end () const {return bar_data_.end ();}
// whatever else
private:
bar_cont_t bar_data_;
};
OK, so I have two (completely unrelated, different project) classes using iterators now. One has iterator and reverse_iterator working as intended, and the other, current one has iterator and a semi-broken const_iterator (specifically, because const_iterator derives from iterator, the code LinkedList<int>::iterator i = const_list.begin() is valid and allows you to modify the const defined list...).
I intend to add all four types to this class... If I can.
How would I proceed to minimize copy/pasting code and changing only the return type? Create a base class like base_iterator to inherit from? Create an iterator or const_iterator and inherit from that? Inherit from some std:: class? If any of these cases are the "best" approach, what code goes where?
Perhaps none of the alternatives are good? I'm quite lost here, and can't find much reference material.
Any advice is appreciated, but please keep in mind that I'm new to the subject (both iterators and C++ in general, especially OOP). I've tried, in vain, to study the header files shipped with GCC - they're not exactly the tutorial I'm looking for.
Sometimes, blanket application of the so-called DRY rule (Don't Repeat Yourself, for those who aren't familiar) is not the best approach. Especially if you're new to the language (C++ and iterators) and OOP itself (methodology), there's little benefit in trying to minimise the amount of code you need to write right now.
I would implement the two iterators using appropriate code for each of them. Perhaps after you have more experience with the language, tools, and techniques, then go back and see whether you can reduce the amount of code by factoring out common code.
It's actually extremely simple.
First of all, take a look at Boost.Iterator library.
Second: you need to declare a base class (it's well explained in the example how to proceed) that will be similar to this one.
template <class Value>
class BaseIterator: boost::iterator_adaptor< ... > {};
You implement the operations to move your pointer around there. Note that because it's an adaptation over an already existing iterator, you can implement it with only a few strokes. It's really impressive.
Third, you simply typedef it with the const and non-const versions:
typedef BaseIterator<Value> iterator;
typedef BaseIterator<const Value> const_iterator;
The library explictly show you how to make the const_iterator version be constructible from the iterator version.
Fourth, for the reverse thing, there is a special reverse_iterator object, that is built on a regular iterator and move backwards :)
All in all, a really elegant and yet fully functional way of defining iterators on custom classes.
I regularly write my own container adaptors, and it's less about DRY than simply saving myself some typing!
Make iterator derive from const_iterator instead of the other way around. Use const_cast appropriately (as an implementation detail, unexposed to users). This works very well in the simple cases and models that "iterators are const_iterators" directly.
When this starts to require clarification comments in your code, then write separate classes. You can use localized macros to generate similar code for you, to avoid repeating the logic:
struct Container {
#define G(This) \
This& operator++() { ++_internal_member; return *this; } \
This operator++(int) { This copy (*this); ++*this; return copy; }
struct iterator {
G(iterator)
};
struct const_iterator {
G(const_iterator)
const_iterator(iterator); // and other const_iterator specific code
};
#undef G
};
That the macro is scoped/localized is important, and, of course, only use it if it actually helps you—if it results in less readable code for you, type it out explicitly.
And about reverse iterators: you can use std::reverse_iterator to wrap your "normal" iterators in many cases, instead of rewriting them.
struct Container {
struct iterator {/*...*/};
struct const_iterator {/*...*/};
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
};
LinkedList<int>::iterator i = const_list.begin() What does your begin method look like? By studying the STL you can see that containers define two such methods with the following signatures:
const_iterator begin() const;
iterator begin();
You should not have a problem getting an iterator from an object qualified as const. I don't think DRY applies here.
Once I used the following approach:
make a template class common_iterator
add typedefs for "iterator" and "const_iterator"
add to "common_iterator" a constructor taking "iterator" type
For "iterator" the additional constructor will replace default copy constructor, in my case it was equivalent to default copy constructor.
For "const_iterator" it will be an additional constructor which allows to construct "const_iterator" from "iterator"
A more concrete version of what maxim1000 suggested:
#include <type_traits>
template<typename Container, bool forward>
class iterator_base
{
public:
using value_type =
typename std::conditional<std::is_const<Container>::value,
const typename Container::value_type,
typename Container::value_type>::type;
iterator_base() { }
// For conversions from iterator to const_iterator.
template<typename U>
iterator_base(const iterator_base<U, forward>& other)
: c(other.c)
{
// ....
}
value_type& operator*() const
{
// ...
}
iterator_base& operator++()
{
if (forward)
{
// ...
}
else
{
// ...
}
}
iterator_base& operator++(int)
{
iterator_base copy(*this);
++*this;
return copy;
}
private:
Container* c = nullptr;
// ...
};
using iterator = iterator_base<self_type, true>;
using const_iterator = iterator_base<const self_type, true>;
using reverse_iterator = iterator_base<self_type, false>;
using const_reverse_iterator = iterator_base<const self_type, false>;