2

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.

zer0ne
  • 403
  • 1
  • 5
  • 13

5 Answers5

3
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.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Then how can I test if an iterator is within a container's range. In the example, b.begin()!=a.end() doesn't mean that b.begin() is a valid iterator of a. – zer0ne Feb 11 '12 at 17:29
  • @zer0ne: I think, you cannot know that (reliably). You need to program this in a such a way that you would not need it in the first place, ever. – Nawaz Feb 11 '12 at 17:31
1

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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
0

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
0

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.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

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);

UncleBens
  • 40,819
  • 6
  • 57
  • 90