This question already has answers here:
How to make my custom type to work with "range-based for loops"?
(10 answers)
Closed 4 years ago.
i have two objects that look like this:
class Object
{
};
class List
{
public:
std::vector<std::shared_ptr<Object>> list;
};
and i want to be able to create a range-based Loop like this:
for (Object& object : list) {
//...
}
for that i created the begin() and end() members:
class List
{
public:
std::vector<std::shared_ptr<Object>> list;
Object& begin() { return *list.begin()->get(); }
const Object& begin() const { return *list.begin()->get(); }
Object& end() { return *list.end()->get(); }
const Object& end() const { return *list.end()->get(); }
};
but the IDE is now asking me for the operators *, ++ and != and i dont undestand how i am supossed to implement can i get some help with that?
The range-for protocol requires that begin() and end() return a type that conforms to the standard library's Iterator concept. This includes things like being able to dereference the iterator with operator*, and increment it with operator++.
An object reference, as you are returning from your begin(), does not meet these requirements, which is what the compiler is complaining about.
Writing your own iterator is quite tedious, especially if you want to support all the random-access facilities that std::vector's iterators offer. There are libraries that can make this a bit easier though.
A far more straightforward way to do things would be to simply forward the iterators that your list member already gives, i.e.
class List
{
public:
using iterator = std::vector<shared_ptr<Object>>::iterator;
using const_iterator = std::vector<shared_ptr<Object>>::const_iterator;
std::vector<std::shared_ptr<Object>> list;
iterator begin() { return list.begin(); }
const_iterator begin() const { return list.begin(); }
iterator end() { return list.end(); }
const_iterator end() const { return list.end(); }
};
Now you can use a List in a range-for loop by saying
for (auto& optr : list) {
do_something_with_object(*optr);
}
The only catch is that dereferencing the iterator will give you a reference to a shared_ptr<Object>, not an Object&, so you'll need to dereference this again to actually use the object itself (as above).
Range-based for loops need more than some arbitrary objects to be returned by begin() and end().
for (auto& object : list) { ... }
for your list is per definition more or less equivalent to the following:
{
auto iterator = list.begin();
auto end = list.end();
for ( ; iterator != end; ++iterator)
{
auto& object = *iterator;
...
}
}
That is why your compiler asks you for all these operations (!=, ++, *), as it tries to apply them to the object returned by begin().
The problem is that you are expected to provide an iterator, not just the first and last element of your list. You cannot give the compiler two different Objects and expect it to be able to loop through a list of pointers to them (which is not necessarily visible from the loop).
Tristan provided you with some options in his answer. I would add that the ranges library can remove a lot of the tedium from writing your own iterators, but it is most likely overkill for your situation and probably not the easiest to get accustomed with.
If you grasped previous replies, I rewrite them in a short snippet. No need for any typedef:
class List {
public:
std::vector<std::shared_ptr<Object>> list;
auto begin() { return list.begin(); }
auto begin() const { return list.begin(); }
auto end() { return list.end(); }
auto end() const { return list.end(); }
};
Related
I have a created a dynamic container for storing bytes as uint8_t and have implemented an iterator class inside the container class. The iterator traverses through the bytes as expected. But these bytes are formatted with headers so I want to implement another iterator to skip through the byte array based on byte headers. Is it possible to have both iterator classes run when used with for(auto &x: container) based on which class is declared first? or is there some other way to do this?
Current iterator and container
class container {
public:
container() : s(0), c(2), data(new uint8_t[2]) {};
container(size_t reserve) : s(0), c(reserve + 1), data(new uint8_t[reserve + 1]) {};
~container();
// container insert, operators etc...
// iterator class
class Iterator {
public:
uint8_t* ptr;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = uint8_t;
using pointer = uint8_t*;
using reference = uint8_t&;
Iterator(pointer m_ptr) : ptr(m_ptr) { std::cout << "In iterator 1" << std::endl; }
reference operator*() const;
pointer operator->();
Iterator& operator++();
Iterator operator++(int);
Iterator& operator+=(int const& lhs);
Iterator operator+(int const& lhs);
friend bool operator== (const Iterator& a, const Iterator& b);
friend bool operator!= (const Iterator& a, const Iterator& b);
};
Iterator begin() const;
Iterator end() const;
private:
uint8_t* data;
size_t s;
size_t c;
};
You can't make iteration behave differently based on declaration order. What would that even mean? Somehow, that's like asking for the same class to behave differently based on context or perhaps randomly.
However, what you can do is implement code like this:
for (auto x: my_container.raw()) { ... }
for (auto x: my_container.rows()) { ... }
for (auto x: my_container.columns()) { ... }
for (auto x: my_container.pixels()) { ... }
Note: I'm just guessing that the header you mention is a bitmap header and that you're processing an image file.
BTW: There's a bunch of things wrong with your code that are not catastrophic, but still good opportunities to improve, like e.g. using raw new[] memory allocation which should be a std::vector instead. Consider posting your working (!) code on
codereview.stackexchange.com.
No, range iteration syntax is what it is: a proxy for calling the container's begin() and end(); and then iterating over the returned sequence. That's the only way it works.
C++20 adds the ranges library which can be used to combine two separate iterator sequences together. Until C++20 the most common option in this kind of a situation is to declare a third iterator class that proxies over the combined sequence of the two underlying iterator classes, and then use this proxy iterator class with begin() and end(). The basic approach is fairly straightforward:
begin() returns the third iterator class initialized with the beginning and the ending iterator values of both underlying iterator classes, end() returns the third iterator class initialized with the ending iterator values of both underlying iterator classes for both the proxied, internal beginning and ending iterator values (i.e. begin=end for both of them).
The * and/or the -> operator overloads check if the first iterator class's proxied beginning iterator value already reached its ending value. If not then use it to implement * and ->, otherwise use the second proxied beginning iterator value.
The various forward/increment overloads advance the first iterator class's proxied beginning iterator value. If it's already at its iterator class's ending iterator value then the second iterator class's proxied beginning iterator value gets advanced.
I need to implement iterators, and i don't have time to make nice iterator classes, so i have decided to just return pointers. It is something like this
int* begin()
{
return p;
}
But i want them to behave as usual stl iterators
*++begin(); // doesn't work because returned pointer isn't l-value
std::vector<int> vi{ 0, 1 };
*++vi.begin(); // works fine
int* p = begin();
*++p; // works fine as well
How can i do this?
Pointers do meet the iterator requirements prefectly (a pointer meets even the most-specialised Random access iterator requirements). Your problem comes from the fact that in the implementation of the standard library which you're using, the iterators provided by e.g. std::vector support more operations than the iterator requirements require.
In other words, the standard does not guarantee that ++vi.begin() will work for a std::vector iterator vi. It happens to work on your implementation of the standard library, but it's an implementation detail. An iterator which would not support that is still a perfectly valid iterator.
So, to answer your question: if you want a quick stand-in for an iterator which will support all iterator operations, you can certainly use a pointer. If you want a quick stand-in for an iterator which will additionally support all the operations your standard library implementation supports in addition to iterator requirements, you might have to roll out your own class.
A minimal iterator is quite easy to whip up using boost:
#include <boost/iterator/iterator_facade.hpp>
using namespace boost;
struct Int100
{
int arr[100];
struct iterator : iterator_facade<iterator,int,forward_traversal_tag>
{
iterator( int* p = nullptr ) : p(p) {}
void increment() { ++p; }
bool equal(const iterator& other) const { return p == other.p; }
int& dereference() const { return *p; }
int* p;
};
iterator begin() { return {arr}; }
iterator end() { return {arr+100}; }
};
This supports the *++begin() syntax you where looking for.
I have a class for which internally holds pointers to its own kind in a container as shown below:
class A
{
public:
A(int x) : _data(x){}
A ** begin()
{
return v.empty() ? nullptr : &v[0];
}
A ** end()
{
return v.empty() ? nullptr : &v[0] + v.size();
}
void Display()
{
cout << "Data = " << _data << endl;
}
vector<A *> v;
private:
int _data;
};
Now I want to be able to use the range based for loop on the objects of A and thus have written member begin and end for the same. It works as expected. But if I try to replace the vector with a deque for the reason mentioned here, it crashes when trying to access the last element. If the way in which I have implemented the end() is incorrect, can someone please provide a correct one?
Unlike std::vector, std::deque doesn't guarantee that it stores its elements contiguously. So in general pointer arithmetic will not do the right thing. If you want to use a deque, instead of returning pointers into it, you should return iterators.
If you would actually have the deque as a public member then it might make sense to directly return std::deque<A*>::iterator:
std::deque<A*>::iterator begin()
{
return v.begin();
}
std::deque<A*>::iterator end()
{
return v.end();
}
However, it might (depending on your application) be better to have it as a private member and implementation detail, in which case it would also be better to have A::iterator which could just be a typedef for the iterator into the underlying container type, to avoid inappropriately exposing implementation detail:
typedef std::deque<A*>::iterator iterator;
iterator begin()
{
return v.begin();
}
iterator end()
{
return v.end();
}
Is there a way to use boost foreach without defining a const_iterator?
My use-case for this is an iterator for a vector, that can contain invalid elements. The iterator should traverse the vector, and yield only the valid elements. It should also repair the vector in the sense that it should swap each invalid item with the next valid item and resize the vector at the end. For example if -1 represents invalid values the vector [6,-1,-1,9,-1,2] should iterate over 6,9 and 2 and leave the vector as [6,9,2].
I tried implementing this with boost::iterator_facade but I could not think of a way to implement a const_iterator, because the vector can change by removing invalid values and thus cannot be const.
Separation of concerns: the container is responsible for its invariants, the iterators for traversal. If you move the repairing to the container you can separate the logical const from the mutable, hidden parts.
Can you write your iterators the 'dumbest' way possible to disentangle them from the container? For instance storing a numerical index (if it makes sense for your container), then calling a private friend (or more) of the container to access the logical n-th element.
The private friend(s) can then be overloaded on const and may still modify the mutable parts to do the repairing you describe, and then return the element.
An (abridged) example of container supporting random-access (and thus numerical indices for access):
template<typename T>
class vector {
mutable std::vector<std::weak_ptr<T>> data; // notice mutable
T&
fetch(int n);
T const&
fetch(int n) const; // notice const overload
public:
class const_iterator;
friend class const_iterator;
const_iterator
begin() const;
};
template<typename T>
class vector<T>::const_iterator {
int index;
vector<T> const* this_; // notice const
public:
// constructors go here
const_iterator&
operator++()
{ ++index; }
// ...
T const&
operator*() const
{ return this_->fetch(index); } // this will call the const version of fetch
};
// example implementation of the const version of fetch
template<typename T>
T const&
vector<T>::fetch(int n) const
{
auto removed = std::remove_if(data.begin(), data.end(), [](std::weak_ptr<T>& element)
{ return element.expired(); });
// mutate mutable data member in a logically const member
data.erase(data.begin(), removed);
// this assumes that there is no race condition
// bear with me for the sake of understanding the mutable keyword
return *data[n].lock();
}
All forms of "foreach" are specifically for iterating over every element of a container. You are not just iterating over every element of a container. You are modifying the container as you iterate.
So just write a regular for-loop. There's no need for special cleverness or anything.
Here's the code for it:
std::vector<int> it = vec.begin();
for(; it != vec.end;)
{
if(*it < 0)
{
it = vec.erase(it);
continue;
}
else
{
//Do stuff with `it`.
++it;
}
}
See, just a simple loop. No need for fancy iterator facades or other such gimmickry.
More or less everything is in the topic.
when I have
func(my_cont& c)
{ c.begin() };
things works, but
func(const my_cont& c)
{ c.begin() };
doesn't work, compiler claims that cant convert this to const my_cont<..>.... from my_cont<..>
What are the requirements for the container and my custom iterator to handle this?
Your container class should have two implementations of begin() and end() - one returning iterator and another returning const_iterator:
iterator begin ();
const_iterator begin () const;
iterator end ();
const_iterator end () const;
const_iterator cannot be used to modify the object that it points to.
You need to add a const begin(), something like this:
class my_cont
{
public:
const_iterator begin() const;
};
You'll have to define a const_iterator type also.
Here is a good article on writing iterators:
http://www.aristeia.com/Papers/CUJ_June_2001.pdf
As most people have noted you need a const version of begin() and end() that returns a const_iterator.
What most people have forgotten is that an iterator should be able to implicitly convert into a const_iterator. Otherwise it is hard to get a const iterator from a non cost object (without a lot of nasty casts).
my_cont data;
for(my_cont::const_iterator loop = data.begin(); loop != data.end(); ++loop)
{
/* STUFF */
}
Note above: The above calls will actually call the non cost versions begin() and end(). But they are being assigned to a const_iterator. So your iterators must be convertible to const_iterator for the above code to work (Note: There is no implicit conversion from const_iterator to iterator. That should take an explicit const_cast as it is inherently dangerous).
class my_cont
{
public:
class iterator { /* STUFF */ }
class const_iterator
{
public: const_iterator(iterator const& rhs);
/* STUFF */
}
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
/* STUFF*/
};
This is exactly the reason why STL implements cost_iterator for containers. Just check how is it implemented in STL, I'm not sure you can find any better solution.
You can only call const member functions on a const object (or volatile member functions on a volatile object).
The compiler is telling that iterator my_cont::begin() const is missing.