user1599559
user1599559

Reputation:

Strange Iterator Behavior in g++ 4.7.1

I originally wrote some code that uses std::array in Microsoft VS 2012. However, when porting it to g++ 4.7.1, some issues arose. It was narrowed down to what seems like a difference in iterator behavior between the platforms.

The following is a basic code sample that isolates the difference:

#include <vector>
#include <array>
#include <iostream>

const std::array<std::array<int, 3>, 3> scoring_segment_codes_ = {{
    {{1, 2, 0}}, {{3, 4, 0}}, {{99, 100, 0}}
}};

int main()
{
    // This works on: g++ (Debian 4.5.3-9) 4.5.3
    // and Visual Studio 2012 (Windows 8)
    // but NOT g++ (Debian 4.7.1-7) 4.7.1
    for (auto i = scoring_segment_codes_.at(1).begin(); *i != 0; ++i)
    {
        std::cout << "Bad: " << *i << std::endl;
    } 

    std::cout << std::endl;

    // works on all three
    for (unsigned i = 0; i < scoring_segment_codes_.at(1).size(); i++) {
        std::cout << "Good: " << scoring_segment_codes_.at(1)[i] << std::endl;
    }

    std::cout << std::endl;

    // works on all three
    auto bees = scoring_segment_codes_.at(1);
    for (auto i = bees.begin(); *i != 0; ++i)
    {
        std::cout << "Good: " << *i << std::endl;
    }
    return 0;
}

The output of this sample on g++ (Debian 4.5.3-9) 4.5.3 and Microsoft VC++ is:

Bad: 3
Bad: 4

Good: 3
Good: 4
Good: 0

Good: 3
Good: 4

This was what I was expecting the output to be. However, g++ (Debian 4.7.1-7) 4.7.1 produces the output:

Bad: 3
Bad: 32513
Bad: 6297664

Good: 3
Good: 4
Good: 0

Good: 3
Good: 4

The difference seems to be the way the iterator is retrieved. I had thought the result of auto i = scoring_segment_codes_.at(1).begin() is defined behavior. Is it not? Or is the problem something else completely?

If this is an issue with g++ 4.7.1, unfortunately this is a system I am stuck with. Is there anything I can get this to work on g++ 4.7.1? I know going through and making sure all the iterator usage is compatible is an option, but this seems like a very rich source of bugs.

Upvotes: 3

Views: 269

Answers (1)

Yuushi
Yuushi

Reputation: 26040

As per my comment, the version of g++ I'm using is slightly different (4.7.0). Looking at the actual code for their array implementation gives this:

#ifdef __EXCEPTIONS
  constexpr const_reference
  at(size_type __n) const
  {
return __n < _Nm ? 
       _M_instance[__n] : throw out_of_range(__N("array::at"));
  }
//#else path

From Constexpr GCC Wiki, we have that "A constant expression is noexcept even if it involves a call to a constexpr function that isn't declared noexcept.". Thus it looks like someone has erroneously labelled at as constexpr even though it can throw.

I'm not sure if this has been related as a bug report, but it certainly looks like a bug. A temporary fix is to use operator[] - but this looks like the reason operator[] works and at does not.

Edit: To answer the comment, modifying the standard library header is one possibility, although that certainly has problems.

The other is to perhaps create a wrapper like the following (note that I haven't tested this):

template <typename T, std::size_t sz>
const T& at(const std::array<T, sz>& arr, typename std::array<T, sz>::size_type index)
{
    if(index < arr.size())
        return arr[index];
    throw std::out_of_range("array::at");
}

Similarly for the non-const reference version. Note that this can call array.at() directly as the non-const version of at() is correct.

Neither of these solutions is perfect, but they may at least help you work around this bug.

2nd Edit: Looking into it more, apparently I'm wrong, it is legal to declare constexpr functions that throw utilizing a ternary operator. I'll leave the fix here because it works, but my analysis of what's going on is incorrect.

Upvotes: 2

Related Questions