Recently I came across this code in my codebase (Simplified for here, of course)
auto toDelete = std::make_shared<std::string>("FooBar");
std::vector<decltype(toDelete)> myVec{toDelete};
auto iter = std::find_if(std::begin(myVec), std::end(myVec),
[](const decltype(toDelete) _next)
{
return *_next == "FooBar";
});
if (iter != std::end(myVec))
{
std::shared_ptr<std::string> deletedString = iter[0];
std::cout << *deletedString;
myVec.erase(iter);
}
Online Example
Now, I noticed that here we are accessing an iterator by indexing!
std::shared_ptr<std::string> deletedString = iter[0];
I've never seen anyone access an iterator by indexing before, so all I can guess at is that the iterator gets treated like a pointer, and then we access the first element pointed to at the pointer. So is that code actually equivalent to:
std::shared_ptr<std::string> deletedString = *iter;
Or is it Undefined Behavior?
From the cppreference documentation for RandomAccessIterator:
Expression: i[n]
Operational semantics: *(i+n)
Since a std::vector's iterators meet the requirements of RandomAccessIterator, indexing them is equivalent to addition and dereferencing, like an ordinary pointer. iter[0] is equivalent to *(iter+0), or *iter.
This is Standard conforming behavior
24.2.7 Random access iterators [random.access.iterators]
1 A class or pointer type X satisfies the requirements of a random access iterator
if, in addition to satisfying the requirements for bidirectional
iterators, the following expressions are valid as shown in Table 118.
a[n] convertible to reference: *(a + n)
Note that it is not required that the particular iterator is implemented as a pointer. Any iterator class with an overloaded operator[], operator* and operator+ with the above semantics will work. For std::vector, the iterator category is random access iterator, and it it required to work.
Related
I would like to know if it is possible to obtain an iterator to an object inside a container (e.g. std::vector<...>) by only having access to the object inside the container, e.g. through a reference (which implies we have access to a pointer to it using the & operator). For example, normally we declare an iterator as
std::vector<int>::iterator = vec.begin();
or
std::vector<int>::iterator = next(vec.begin(), idx);
but in the first example we are most probably about to iterate through the container, in order, while in the second example we know the index of the object we require. I would like to know if we can obtain the iterator to an object without knowing at which index it resides in the container, but if we do have a reference or a pointer to it, as explained above.
It might appear that this question has already been asked here, but it seems more like the OP wanted others to fix his code, rather than answering the general question, so the answers are not so satisfactory in my opinion. Also, the answer here seems to say that we can initialize an iterator with a constructor, as shown below
std::vector<int>::iterator it(...);
but I have not been able to find any evidence of a constructor for the std::iterator class in the official documentation (and neither have I been able to find any documentation on std::vector<...>::iterator) so I am wary to use the constructor shown above, even if it compiles.
NOTE
I use std::vector as an example above, but ideally I would like this to work for any container, e.g. std::list or std::deque
Specifically for std::vector (and other contiguous containers like std::string), given a pointer to an object in the vector p, we can simply do:
auto iter = v.begin() + std::distance(v.data(), p);
This is guaranteed by the contiguity contract. Note that random access is insufficient here, the above will not work for std::deque.
For any other container, there's no easy way of doing this. You'd have to just use find_if:
auto iter = std::find_if(c.begin(), c.end(), [p](auto const& o) { return &o == p; });
For intrusive containers, the iterator will be encoded into the object itself somehow so there will be some direct mechanism for converting p to an iterator. But that will be dependent on the intrusive container itself.
You can use the find function---it returns an iterator---, supported on (almost?) all containers, to find your objects. If there are several objects which are equal under the operator==, iterate until the one with the same address has been found.
Since C++11 you can use the keyword auto to deduce the type, it makes writing the type easier.
If we know the index we can get an iterator to it bybegin() + index.
And if we don't know the index we could use the std::distance() from begin and the other iterator, which will give us an iterator to the same element.
// if we know the index
auto it1 = begin(vec) + 4;
cout << *it1;
// if we don't know the index
auto p = begin(vec) + 3;
auto it2 = begin(vec) + distance(begin(vec), p+1);
cout << *it2;
I would like to know if it is possible to obtain an iterator to an object inside a container (e.g. std::vector<...>) by only having access to the object inside the container, e.g. through a reference (which implies we have access to a pointer to it using the & operator). For example, normally we declare an iterator as
std::vector<int>::iterator = vec.begin();
or
std::vector<int>::iterator = next(vec.begin(), idx);
but in the first example we are most probably about to iterate through the container, in order, while in the second example we know the index of the object we require. I would like to know if we can obtain the iterator to an object without knowing at which index it resides in the container, but if we do have a reference or a pointer to it, as explained above.
It might appear that this question has already been asked here, but it seems more like the OP wanted others to fix his code, rather than answering the general question, so the answers are not so satisfactory in my opinion. Also, the answer here seems to say that we can initialize an iterator with a constructor, as shown below
std::vector<int>::iterator it(...);
but I have not been able to find any evidence of a constructor for the std::iterator class in the official documentation (and neither have I been able to find any documentation on std::vector<...>::iterator) so I am wary to use the constructor shown above, even if it compiles.
NOTE
I use std::vector as an example above, but ideally I would like this to work for any container, e.g. std::list or std::deque
Specifically for std::vector (and other contiguous containers like std::string), given a pointer to an object in the vector p, we can simply do:
auto iter = v.begin() + std::distance(v.data(), p);
This is guaranteed by the contiguity contract. Note that random access is insufficient here, the above will not work for std::deque.
For any other container, there's no easy way of doing this. You'd have to just use find_if:
auto iter = std::find_if(c.begin(), c.end(), [p](auto const& o) { return &o == p; });
For intrusive containers, the iterator will be encoded into the object itself somehow so there will be some direct mechanism for converting p to an iterator. But that will be dependent on the intrusive container itself.
You can use the find function---it returns an iterator---, supported on (almost?) all containers, to find your objects. If there are several objects which are equal under the operator==, iterate until the one with the same address has been found.
Since C++11 you can use the keyword auto to deduce the type, it makes writing the type easier.
If we know the index we can get an iterator to it bybegin() + index.
And if we don't know the index we could use the std::distance() from begin and the other iterator, which will give us an iterator to the same element.
// if we know the index
auto it1 = begin(vec) + 4;
cout << *it1;
// if we don't know the index
auto p = begin(vec) + 3;
auto it2 = begin(vec) + distance(begin(vec), p+1);
cout << *it2;
That is, providing that container is not empty, can I safely do this:
std::vector<int> container;
container.push_back( 0xFACE8D );
auto last = container.end() - 1;
and this:
EDIT: replaced -1 with -- here:
std::list<int> container;
container.insert( 0xFACE8D );
auto last = container.end();
--last;
and again for arbitrary non-empty container?
EDIT: let me clarify the question.
Sometimes perfectly legal code behaves incorrectly. The question is: assuming that the above code compiles, is it safe to do something like that?
It should be safe for ordinary C-style arrays because the corresponding iterators are just pointers. But is it safe for more complicated containers?
Suppose that one implements list with iterators like this one:
class MyListIterator {
MyListIterator *prev, *next;
MyListIterator * operator--() { return prev; }
...
};
class MyList {
MyListIterator *end() { return NULL; }
...
};
Then an attempt to decrement container::end(), despite being perfectly legal syntactically, would cause a segfault.
I hope, though, that stl containers are much smarter than that. Thus the question on the guarantees on the above stl::list code behavior, if any.
std::vector returns random-access iterators, so yes, this is safe with vector.
std::list returns bidirectional iterators. They can be incremented (++) and decremented (--), but don't allow for arbitrary arithmetic (+, -) or relative comparisons (>, <). It's not safe with list.
You can use std::advance on bidirectional iterators to advance or rewind arbitrary amounts. In C++11 you can use std::prev in place of iter - 1.
The C++ standard says that std::vector<T>::iterator is random access iterator pointing to the past-the-end element. So your can safely use - and + operators.
Unlike std::vector, std::list<T>::iterator is bi-directional iterator that support only -- and ++ operators.
I experimented the following code
list<int> a ={1,2};
list<int> b ={3,4};
a.erase(b.begin());
for (auto x:b) cout << x << endl;
Strangely, the program ran fine without any error. What it prints out is 4. I wonder why erase is a member function when the object is already implicit in the iterator.
a.erase(b.begin());
This invokes undefined behavior, because you're passing iterator obtained from one container to a function of other container. a and b are two different containers, they're not same.
Undefined behavior means anything could happen: it may run as expected, or it may not. Neither the language specification nor the compiler gives guarantee that it will work. It is aptly said "undefined behavior".
What you should do is this:
auto value = *(b.begin()); //value is int
auto it = std::find(a.begin(), a.end(), value); //returns iterator
if ( it != a.end())
a.erase(it); //well-defined, as the iterator belongs to the same container!
Or, if you want to remove all elements equal to value, then you could simply do this:
a.remove(value); //std::list has remove member function
However, if you use std::vector which you should be using in most cases. It is default container type in C++, and you should use std::list only if you've strong reason to do so:
std::vector<int> a ={1,2};
std::vector<int> b ={3,4};
//if you want to remove one element:
auto value = *(b.begin()); //value is int
auto it = std::find(a.begin(), a.end(), value); //returns iterator
if ( it != a.end())
a.erase(it); //well-defined, as the iterator belongs to the same container!
And if you want to remove all elements equal to value, then you can apply popular Erase-Remove Idiom as:
a.erase(std::remove(a.begin(), a.end(), value), a.end());
Note that std::vector doesn't have remove() member function, that is why you apply this idiom. You can read my answer here which discusses about this in more detail.
This is simply undefined behaviour, so anything can happen, and anything that you observe is in some way the "expected behaviour".
Don't do this.
The precondition for std::list::erase is clearly that the argument be an iterator to an element of the container.
C++ is a Standard that doesn't require an iterator know what container it belongs to. We cannot change the Standard just because on one particular implementation a function can do its work without needing a particular parameter.
Other have mentioned that according to the C++ standard this results in undefined behaviour.
However, the beauty of double-linked lists is that removing a node from a list only requires a pointer to that node, no need to refer the container itself, e.g.:
template<class Tag>
inline void unlink(ListNode<Tag>* node)
{
ListNode<Tag> *prev = node->prev_, *next = node->next_;
prev->next_ = next;
next->prev_ = prev;
node->prev_ = node;
node->next_ = node;
}
Your code happens to work correctly because std::list<> is often implemented as a double-linked list and list<>::erase() gets a pointer to the list node from the iterator and executes code similar to the above. If you enable debug support for iterators though, this code will probably result in a run-time assertion.
It is just an implementation detail, if std::list::erase doesn't actually require knowledge of this. It is a member function for consistency with other containers. As it is, you can take the container as a template argument and call container.erase(iter);
Does the C++ Standard say I should be able to compare two default-constructed STL iterators for equality? Are default-constructed iterators equality-comparable?
I want the following, using std::list for example:
void foo(const std::list<int>::iterator iter) {
if (iter == std::list<int>::iterator()) {
// Something
}
}
std::list<int>::iterator i;
foo(i);
What I want here is something like a NULL value for iterators, but I'm not sure if it's legal. In the STL implementation included with Visual Studio 2008, they include assertions in std::list's operator==() that preclude this usage. (They check that each iterator is "owned" by the same container and default-constructed iterators have no container.) This would hint that it's not legal, or perhaps that they're being over-zealous.
OK, I'll take a stab. The C++ Standard, Section 24.1/5:
Iterators can also have singular
values that are not associated with
any container. [Example: After the
declaration of an uninitialized
pointer x (as with int* x;), x must
always be assumed to have a singular
value of a pointer. ] Results of most
expressions are undefined for singular
values; the only excep- tion is an
assignment of a non-singular value to
an iterator that holds a singular
value.
So, no, they can't be compared.
This is going to change in C++14. [forward.iterators] 24.2.5p2 of N3936 says
However, value-initialized iterators may be compared and shall compare
equal to other value-initialized iterators of the same type.
I believe you should pass a range to the function.
void fun(std::list<int>::iterator beg, std::list<int>::iterator end)
{
while(beg != end)
{
// do what you want here.
beg++;
}
}
Specification says that the postcondition of default constructor is that iterator is singular. The comparison for equality are undefined, so it may be different in some implementation.