1

I have encountered a problem while I was using a loop like this,

//vector<int> sides;
for (int i = 0; i < sides.size()-2; i++) {
  if (sides[i] < sides[i+1] + sides[i+2]) {
    ...
  }
}

The problem was that the size() method uses an unsigned number. So, vectors of size less than 2 make an undefined outcome.

I understand that I should have used an unsigned variable for the loop but it doesn't solve the problem. So I had to deal with it by typecasting or using some conditions.

My question is that why do STL uses an unsigned int to eliminate negative index access violation and generating more problems?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Sumsuddin Shojib
  • 3,583
  • 3
  • 26
  • 45
  • 11
    How about `i + 2 < sides.size()`? – LogicStuff Mar 06 '18 at 11:32
  • An unsigned index type does incur inconvenience. And I can't see any benefit of this choice. – llllllllll Mar 06 '18 at 11:35
  • 1
    yes, but why not simply the signed type. I could loop in reverse order. – Sumsuddin Shojib Mar 06 '18 at 11:36
  • 2
    unsigned actually make any subtraction operation dangerous. – Sumsuddin Shojib Mar 06 '18 at 11:39
  • 2
    `int` cannot hold all valid indexes that a `vector` can have, so you can't iterate over all `vector`s with it, which makes it a poor choice. [`std::size_t`](http://en.cppreference.com/w/cpp/types/size_t) is specifically defined to be the proper size. – nwp Mar 06 '18 at 11:42
  • 3
    As to why `std::size_t` is unsigned: I guess people thought that negative sizes make no sense and wasting a bit is not useful. In hindsight a case could be made that making `std::size_t` signed would have been better. – nwp Mar 06 '18 at 11:43
  • 3
    @nwp There is really no excuse for the unsigned-ness of `size_t`. No container can contain more than `pow(2, 63)` elements. The only result of this unsigned-ness is more pitfalls. – llllllllll Mar 06 '18 at 11:47
  • @liliscent [yet](https://skeptics.stackexchange.com/questions/2863/did-bill-gates-say-640k-ought-to-be-enough-for-everyone) – nwp Mar 06 '18 at 11:49
  • @liliscent: I'd rather get wraparound than UB. – Lightness Races in Orbit Mar 06 '18 at 11:53
  • @LightnessRacesinOrbit If `size_t` was a signed type, there would be no UB. – llllllllll Mar 06 '18 at 11:56
  • @LightnessRacesinOrbit Why? UB has a chance to crash or be detected by UBsan while a wraparound is a logic error in this case that is much more difficult to find. – nwp Mar 06 '18 at 11:56
  • @nwp Your link doesn't make sense. 63 bit is only an example when usual machine has a maximum 64 bit memory(or 48 bit) virtual memory space. If this number grows, the length of both signed and unsigned types also grow. – llllllllll Mar 06 '18 at 11:59
  • 1
    It is largely considered to be a mistake these days. See https://stackoverflow.com/questions/10168079/why-is-size-t-unsigned – juanchopanza Mar 06 '18 at 12:01
  • @nwp: Conversely, relying on UB is about the worst decision you can make, whereas wraparound is reasonably easy to detect, particularly if you're in a debugging session. If accessing an array index 9,223,372,036,854,775,808 is "difficult to find", then there is something very wrong with your debugging methodology. – Lightness Races in Orbit Mar 06 '18 at 12:02

2 Answers2

6

It was decided that containers would use unsigned indexes and sizes way back in the 90s.

On the surface this seemed wise; I mean, sizes and indexes into containers cannot be negative. It also permits slightly larger max values, especially on 16 bit systems.

It is now considered a mistake; your code is but one of the many reasons why. std2 will almost certainly use ptrdiff_t, the signed partner of size_t, for sizes and indexes.

Note that 1u-2 is defined behaviour; it is -1 cast to unsigned, which is guaranteed to be the max unsigned value of that type.

You can fix your code in many ways, including:

for (int i = 0; i+2 < sides.size(); i++) {
  if (sides[i] < sides[i+1] + sides[i+2]) {

or

for (int i = 0; i < (std::ptrdiff_t)sides.size()-2; i++) {
  if (sides[i] < sides[i+1] + sides[i+2]) {

this second one can break on containers near the size of your memory space limit; on 64 bit systems this is not a problem, on 32 bit systems and vectors of char there is a slim chance you could create a vector large enough.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
4

The reason is that the operator sizeof that determines the size of any object returns a value of the type size_t. So for example also the standard C functiuon strlen has return type size_t.

As result it is adopted that standard containers also returns their sizes as values of the type size_t.

As for the loop then it can be rewritten for example the following way

for ( size_t i = 0; i + 2 < sides.size(); i++) {
  if (sides[i] < sides[i+1] + sides[i+2]) {
    ...
  }
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335