2

This is a very specific issue and is a bit long to explain, so please bear with me as I try to summarize this as best I can.

I create 2 user types, the first is used inside the second :

struct mySubType {
    int val;
    mySubType () {}
    mySubType ( int _val) : val(_val){}
    bool operator!=(const mySubType& rhs) const { return val != rhs.val; }
};

struct myType {
    mySubType start;
    mySubType stop;
    myType () {}
    myType (mySubType _start, mySubType _stop) : start(_start), stop(_stop) {}
};

In main(), I create a vector of unique_ptr to myType and fill it as such :

vector<unique_ptr<myType>> v;

for (int i = 0; i < 10; ++i)
    v.push_back( unique_ptr<myType>(new myType( mySubType(i), mySubType(i+1))) );

So the val of start and stop for each element is as follows :

start:0 stop:1
start:1 stop:2
start:2 stop:3
start:3 stop:4
start:4 stop:5
start:5 stop:6
start:6 stop:7
start:7 stop:8
start:8 stop:9
start:9 stop:10

start should always be the same as previous stop (this is the case in this example). To check this, I tried the following :

for (auto it = v.begin(); it != --v.end(); )
{
    if ((*it)->stop != (*(++it))->start)
        cout << "1";
}

To my surprise, the output was : 111111111, all different when they should be all equal.

I tried a few other things to try and understand the source of the error. I replaced the inside of the loop with

mySubType stop = (*it)->stop;
mySubType next_start = (*(++it))->start;
if (stop != next_start)
    cout << "2";

and then

if ((*it)->stop.val != (*(++it))->start.val)
    cout << "3";

Neither of those printed anything (all tests were correctly solved as equal).

This only happens when using unique_ptr (vector<myType> does not have the same issue). I also tried using post-increment but I get the exact same result.

Does anyone have a clue why this happens ?

I know there are a lot of ways to work around this issue (the 2 above for example). What I'm interested in is why this behaviour happens.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 1
    `for (auto it = v.begin(); it != --v.end(); )` - what should this even mean? – Denis Sheremet Nov 21 '19 at 11:02
  • 4
    ```((*it)->stop != (*(++it))->start)``` are you sure that part to the left of ```!=``` will be evaluated before the part to the right of ```!=``` ? https://en.cppreference.com/w/cpp/language/eval_order – Andrew Kashpur Nov 21 '19 at 11:03
  • See also: [`std::adjacent_find`](https://en.cppreference.com/w/cpp/algorithm/adjacent_find) and [`std::adjacent_difference`](https://en.cppreference.com/w/cpp/algorithm/adjacent_difference) – Caleth Nov 21 '19 at 11:26
  • @AndrewKashpur I see, but then why does adding ```.val``` change anything ? Pure coincidence ? – Jeremy Maille Nov 21 '19 at 12:29
  • @DenisSheremet iterate until element before last. The increment happens inside the loop, hence why there is no ++it. Is it not a valid syntax ? – Jeremy Maille Nov 21 '19 at 12:31
  • 1
    Well, I found [this thread](https://stackoverflow.com/questions/5322104/how-portable-is-end-iterator-decrement) and it looks contradictory, on the one hand it seems to be common technique for stl containers, on the other hand, it's not guaranteed to be implemented in a such way for all kinds of iterators. I'd suggest `std::next` here to avoud ambiguity. – Denis Sheremet Nov 22 '19 at 05:28

1 Answers1

4

The expression in the if statement

    if ((*it)->stop != (*(++it))->start)

has undefined behavior.

It seems you mean the following loop

#include <iterator>

// ...

for (auto it = v.begin(); it != v.end(); ++it )
{
    auto next = std::next( it );
    if ( next != v.end() && (*it)->stop != (*( next ))->start)
        std::cout << "1";
}  

Instead of the for loop you could use the standard algorithm std::adjacent_find. For example

#include <iostream>
#include <memory>
#include <vector>
#include <iterator>
#include <algorithm>

struct mySubType {
    int val;
    mySubType () {}
    mySubType ( int _val) : val(_val){}
    bool operator!=(const mySubType& rhs) const { return val != rhs.val; }
};

struct myType {
    mySubType start;
    mySubType stop;
    myType () {}
    myType (mySubType _start, mySubType _stop) : start(_start), stop(_stop) {}
};

int main()
{
    std::vector<std::unique_ptr<myType>> v;

    for (int i = 0; i < 10; ++i)
        v.push_back( std::unique_ptr<myType>(new myType( mySubType(i), mySubType(i+1))) );

    auto it = std::adjacent_find( std::begin( v ), std::end( v ),
                                 []( const auto &left, const auto &right )
                                 {
                                     return left->stop != right->start;
                                 } );

    if ( it != std::end( v ) ) std::cout << "there is an error\n";
    else std::cout << "The sequence is correct\n";
}

The program output is

The sequence is correct
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • I agree that the comparison invokes undefined behavior since the evaluation order is unspecified. But I do not see, per se, a problem with the incrementing in the loop. The iterator is incremented during the comparison and hence exactly once in the loop. – n314159 Nov 21 '19 at 11:22
  • @n314159 I took into account that within the body of the loop there is undefined behavior,:) – Vlad from Moscow Nov 21 '19 at 11:25
  • In this case, ```if ((*it)->stop.val != (*(++it))->start.val)``` has undefined behaviour too, right ? Does it work out of pure chance, then ? – Jeremy Maille Nov 21 '19 at 12:33
  • @JeremyMaille I am sorry. I have not understood your question. I showed how the loop can be changed or substituted for the standard algorithm std::adjacent_find. – Vlad from Moscow Nov 21 '19 at 12:43
  • @VladfromMoscow Yes, however I did write "I know there are a lot of ways to work around this issue (the 2 above for example). What I'm interested in is _why_ this behaviour happens." So my question is : why does `if ((*it)->stop != (*(++it))->start)` yield wrong results but `if ((*it)->stop.val != (*(++it))->start.val)` yields the right results. – Jeremy Maille Nov 21 '19 at 14:41
  • 1
    @JeremyMaille It depends on the object code generated by the compiler. In the first case there is used a function order of evaluation of its arguments is unspecified. – Vlad from Moscow Nov 21 '19 at 14:45