views:

581

answers:

1

I know that boost or compiler should be last to blame, but I can't see another explanation here. I'm using msvc 2008 SP1 and boost 1.43.

In the following code snippet execution never leaves third BOOST_FOREACH loop

typedef Graph<unsigned, unsigned>::VertexIterator Iter;

Graph<unsigned, unsigned> g;
g.createVertex(0x66);

// works fine
Iter it = g.getVertices().first, end = g.getVertices().second;
for(; it != end; ++it)
    ;

// fine
std::pair<Iter, Iter> p = g.getVertices();
BOOST_FOREACH(unsigned handle, p)
    ;

// fine
unsigned vertex_count = 0;
BOOST_FOREACH(unsigned handle, g.getVertices())
    vertex_count++;

// oops, infinite loop
vertex_count = 0;
BOOST_FOREACH(unsigned handle, g.getVertices()) 
    vertex_count++;

vertex_count = 0;
BOOST_FOREACH(unsigned handle, g.getVertices())
    vertex_count++;

// ... last block repeated 6 times

Iterator code:

class Iterator 
    : public boost::iterator_facade<Iterator, unsigned const, 
                boost::bidirectional_traversal_tag>
{
public:
    Iterator()
        : list(NULL), handle(INVALID_ELEMENT_HANDLE)
    {}

    explicit Iterator(const VectorElementsList &list, unsigned handle = INVALID_ELEMENT_HANDLE)
        : list(&list), handle(handle)
    {}

    friend std::ostream& 
    operator<<(std::ostream &s, const Iterator &it)
    {
        s << "[list: " << it.list <<", handle: " << it.handle << "]";
        return s;
    }

private:
    friend class boost::iterator_core_access;

    void increment()
    {
        handle = list->getNext(handle);
    }

    void decrement() 
    {
        handle = list->getPrev(handle);
    }

    unsigned const& dereference() const
    {
        return handle; 
    }

    bool equal(Iterator const& other) const
    {
        return handle == other.handle && list == other.list;
    }

    const VectorElementsList<T> *list;
    unsigned handle;
};

Some ASM fun:

    vertex_count = 0;
    BOOST_FOREACH(unsigned handle, g.getVertices())
// initialization
013E1369  mov         edi,dword ptr [___defaultmatherr+8 (13E5034h)] // end iterator handle: 0xFFFFFFFF
013E136F  mov         ebp,dword ptr [esp+0ACh] // begin iterator handle: 0x0
013E1376  lea         esi,[esp+0A8h] // begin iterator list pointer
013E137D  mov         ebx,esi 
013E137F  nop

// forever loop begin
013E1380  cmp         ebp,edi 
013E1382  jne         main+238h (13E1388h) 
013E1384  cmp         ebx,esi 
013E1386  je          main+244h (13E1394h) 
013E1388  lea         eax,[esp+18h] 
013E138C  push        eax
// here iterator is incremented in ram
013E138D  call        boost::iterator_facade<detail::VectorElementsList<Graph<unsigned int,unsigned int>::VertexWrapper>::Iterator,unsigned int const ,boost::bidirectional_traversal_tag,unsigned int const &,int>::operator++ (13E18E0h) 
013E1392  jmp         main+230h (13E1380h) 
        vertex_count++;
// forever loop end

It's easy to see that iterator handle is cached in EBP and it never gets incremented despite of a call to iterator operator++() function.
I've replaced Itarator implmentation with one deriving from std::iterator and the issue persisted, so this is not iterator_facade fault. This problem exists only on msvc 2008 SP1 x86 and amd64 release builds. Debug builds on msvc 2008 and debug/release builds on msvc 2010 and gcc 4.4 (linux) works fine. Furthermore the BOOST_FOREACH block must be repeaded exacly 10 times. If it's repeaded 9 times, it's all OK.

I guess that due to BOOST_FOREACH use of template trickery (const auto_any), compiler assumes that iterator handle is constant and never reads its real value again.

I would be very happy to hear that my code is wrong, correct it and move on with BOOST_FOREACH, which I'm very found of (as opposed to BOOST_FOREVER :).

May be related to: http://stackoverflow.com/questions/1275852/why-does-boost-foreach-not-work-sometimes-with-c-strings

EDIT:

I've prepared simplified project reproducing the problem. No templates, no default params, no nothing. Get it here: http://yabcok.nazwa.pl/ugly3.zip

+1  A: 

Looks to me like this bug in VC++ regarding default values in template functions.

There's a very similar bug here in August 2009 (closed as 'fixed' by M$ in their next release)... it's consistent in tons of ways: it's VC++ specific and works in GCC, results in intermittent failure with default template arguments (but never compile-time issues), and the problem only crops up on the second instantiation.

That said, I can't explain the compiler output, or the magic 10 loops... :-)

VC++ even has an old article on workarounds with templates. with very similar bugs so recently, and how consistent your bug and Color_of_Green's bug seem, it's probably VC++ and not Boost.

My guess? It's choking on these signatures: const T & data = T() in graph_elements_collection.h. MSFT recommends changing that to const T data = T(). To verify that it's related to this compiler bug, try that, or the workarounds posted by MSFT... here...

Robert Karl
Since Visual C++ 2010 is now available, have you tried repeating this with the newer compiler to see if the problem goes away? If so you can be pretty sure it was a bug that is fixed.
Kate Gregory
Not sure why you would ever do `const t data = T()` instead of just `const t`.
DeadMG
@DeadMG: sorry I don't understand, could you elaborate please?
Saul Rennison
You've default constructed a T, so that.. you can copy-construct a T with it? Why not just construct a T?
DeadMG
@Kate not exactly as other changes in the compiler could affect number of loops required to repoduce the problem or some other detail. this bug is very sensitive to stack layout and space radiation@DeadMG with fn(const t data = T()) you can invoke fn without the param@robert thanks for suggestion, but it seems that this is not my bug. I made simpler version of my test case (no defualt params at all) available here: http://geneviz.googlecode.com/files/ugly2.zip
Jacek Ławrynowicz
@DeadMG: `const T data = T();` is the only one that will properly initialize `data` no matter whether `T` is a built-in or a user-defined type.
sbi
Pretty sure that const T data; is a valid default construction for any type.
DeadMG
@DeadMG: (You need to properly attribute comment answers, otherwise we'll not be notified of your answers.) If your substitute `int` for `T`, this becomes `int data;`, which gives no initialization at all.
sbi
@sbi: I never realized that actually ever did anything. Just thought people did for rofls. As for the int data; thing, you are correct. I dislike that primitive types offer no useful default construction.On the other hand, one must question the purpose of a const default-constructed T. You could surely just get the same effect by constructing a new T whenever you need.
DeadMG
sbi
@sbi: Will that reference even remain valid? I actually don't know the rules for temporaries constructed as default arguments. As for const, well, the const was the whole point. Otherwise it would have been vaguely sensical from the beginning.
DeadMG
@DeadMG: The lifetime of temporaries ends _at the end of the full expression_ they are part of - __unless__ they are bound to a `const` reference, in which case their lifetime is extended to _the end of the reference's lifetime_.
sbi
Also note that, in template code, `T obj = T();` makes perfect sense: it creates an object that's initialized using - if it is a user-defined type - the type's default constructor or - if it's a built-in - `0` or whatever is the type's equivalent of it (e.g., `0.0` for `double` etc.). When you don't know whether you're dealing with a UDT or a built-in, there's no alternative to that.
sbi