1

Form 1:

int main()
{
    std::vector<int> array{1, 2, 3, 4, 5};   

    for(auto i = array.begin(); i != array.end();) {      
        if(*i == 2 || *i == 5) {
            i = array.erase(i);
        } else {          
            i++;
        }
    }   
}

Form 2:

int main()
{
    std::vector<int> array{1, 2, 3, 4, 5};   

    for(auto i = array.begin(); i != array.end(); i++) {
        if(*i == 2 || *i == 5) {
            i-- = array.erase(i);
        }
    }   
}

Are these two form identical? Could I go into problems using one or another? (i.e. with every kind of object, such as linked list?).

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • Personally I would write neither of these, I would use a while() rather than a for without the last condition. I think they are identical btw. – BlueTrin Apr 07 '16 at 08:25
  • This is opinion based, there is a std algorithm [`remove_if`](http://en.cppreference.com/w/cpp/algorithm/remove) to do this – EdChum Apr 07 '16 at 08:27
  • @BlueTrin: How would you write the same while? – markzzz Apr 07 '16 at 08:27
  • @RichardHodges: no, I'm deciding which kind of iterators use in my plugin... – markzzz Apr 07 '16 at 08:28
  • I don't think this is opinion based. I pretty sure one is wrong! – Martin Bonner supports Monica Apr 07 '16 at 08:31
  • @RichardHodges: sorry? I don't understand what you mean! – markzzz Apr 07 '16 at 08:34
  • 2
    Form2 does not work, if it supposed to remove 2 and 5 from a vector. The actual resulting vector is 2 3 5. – hank Apr 07 '16 at 08:39
  • Apart from the fact that it doesn't even work in this example, Form 2 definitely won't work with a std::forward_list. – SSJ_GZ Apr 07 '16 at 08:42
  • 2
    Form 2 is UB actually, because `i` gets invalidated by `array.erase`. The copy returned by `i--` gets assigned a valid iterator, but it is a temporary that gets destroyed, leaving `i` invalid. – nwp Apr 07 '16 at 08:42
  • @SSJ_GZ: why it won't work with a forward_list? – markzzz Apr 07 '16 at 08:47
  • @markzzz: Because its iterators only move forward (hence the name), so no `i--`. – Benjamin Lindley Apr 07 '16 at 08:49
  • @markzzz A forward_list's iterator has no operator--() (nor operator--(int)). – SSJ_GZ Apr 07 '16 at 08:50
  • While I'm at it ;) - I believe it is *permissible* for a vector::iterator to be just a plain old T*: on such an implementation, this won't compile (i-- = ... would be assignment to an rvalue). – SSJ_GZ Apr 07 '16 at 08:50
  • @BenjaminLindley: aren't iterators just pointers? I guess they can move whatever I want. Why should I have this constraint to move only ++ and not --? – markzzz Apr 07 '16 at 09:01
  • @markzzz: Pointers are one type of iterator, but not all iterators are pointers. `forward_list` iterators certainly are not. And the reason for the constraint is because `forward_list` is a singly linked list. The nodes only contain pointers to the next node, not the previous. Which might be important to you if you are under some memory constraint. – Benjamin Lindley Apr 07 '16 at 09:09
  • The "which would you use" text is a red-flag for "primarily opinion based", but in this case @EdChum , there is very little opinion involved: one of the options is unambiguously wrong, and one is right. – Martin Bonner supports Monica Apr 07 '16 at 10:54
  • @MartinBonner Then the OP needs to rephrase the question and title, the fact that one form is erroneous is irrelevant, the OP didn't ask why one works and the other doesn't, they're soliciting opinion as to which the community prefers – EdChum Apr 07 '16 at 10:57
  • The main part of the question is "are they the same?" "Which is better" is very much subsidiary (and I have removed it). – Martin Bonner supports Monica Apr 07 '16 at 11:00
  • @BenjaminLindley: so forward_list use forward iterators and simple array (or vector) random access iterators? – markzzz Apr 07 '16 at 11:10
  • @EdChum "Are these vector iteration forms the same?" is absolutely not "opinion based". It is a true/false question.Or has the question been edited since it was closed off? – Richard Hodges Apr 08 '16 at 13:56
  • @RichardHodges see the edit history – EdChum Apr 08 '16 at 13:57

3 Answers3

4

this one, form 3:

#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> array{1, 2, 3, 4, 5};

    auto is_2_or_5 = [](int i) {
        return i == 2 || i == 5;
    };

    array.erase(std::remove_if(array.begin(),
                               array.end(),
                               is_2_or_5),
                array.end());

}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
3
i-- = array.erase(i);

Is undefined behavior both in pre C++11 and C++11.

For more gory details check out:

Even if it was well defined I wouldn't use the i-- construct for anything else than a standalone expression. It is difficult to understand what's happening.

Community
  • 1
  • 1
bolov
  • 72,283
  • 15
  • 145
  • 224
2

Form 2 is broken.

i-- = array.erase(i);

Let's break that down;

i--  

This returns a copy of i, and then decrements i.

array.erase(i) 

This erases the element at i, and then returns the iterator to the element after.

(i--) = (array.erase(i));

This assigns the result of array.erase(i) to the copy returned by i--.

There is an additional complication in that certainly prior to C++11, you don't know whether the decrement was done before or after the call to array.erase(i), and that would have been undefined behaviour. Experimentally, GCC does the decrement before the erase, so you end up erasing the wrong element.

It may help to write the (nearly) equivalent code out:

    auto j = i;
    --i;          // Here??
    auto k = array.erase(i);
    --i;          // Or here?
    j = k;

If you want to do this, write it as:

    const auto j = i;
    --i;
    array.erase(j);

Which makes it much clearer what you are doing.

  • But that doesn't say how the value computation of the left and right operands are sequenced. (My point being that the assignment is to a temporary which is thrown away.) – Martin Bonner supports Monica Apr 07 '16 at 08:46
  • 1
    I think this is all covered by 1.9/15: *"Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined."* – Tony Delroy Apr 07 '16 at 08:48
  • 1
    Oh right. So it still is undefined behaviour! – Martin Bonner supports Monica Apr 07 '16 at 08:51
  • Edit your answer to indicate that this is undefined behaviour – BlueTrin Apr 08 '16 at 15:14