1

I have a for loop in my program that is not stopping:

vector<int> v;
int k = 1;
v.push_back(1);

for(int i=v.size()-1; i>=(v.size()-k); i--){
    cout << i << " " << v.size() - k << endl;
}

When I execute the above program, it keeps running inside the for loop. The output of the for loop is as below:

0 0
-1 0
-2 0
-3 0
-4 0
.
.
.

As per the output, value of i is decreasing and value of v.size()-k is 0. So, i >= v.size()-k is false, which should stop the for loop to execute after the first round, But the for loop doesn't stop executing.

Can someone help me understand this issue?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Dinesh Suthar
  • 148
  • 2
  • 12
  • 3
    _**v.size()-k** is 0_ Well but you check for `i >= 0` this way. So, `i` may become negative. Comparing an `int` with a `size_t` (the return type of `std::vector::size()` - an unsigned type which is possibly larger), does a conversion of `int i` to that type. You know? Due to that fact, it can never be < 0 (like every unsigned type). – Scheff's Cat Jul 17 '20 at 16:12
  • 2
    I bet `(v.size()-k)` is an unsigned integer and `i>=(v.size()-k)` ends up being the same as `((unsigned something)i)>=(v.size()-k))`, so that when `i` is negative, `(unsigned)i` becomes a huge _positive_ number. – ForceBru Jul 17 '20 at 16:12
  • 1
    Probably worth considering what type `v.size()-k` actually is, and how it affects the expression `i>=(v.size()-k)` evaluation. There are reasons you should strive to not mix signed and unsigned types unless you're crystal clear what happens when you do; this would be exemplary of one such reason. – WhozCraig Jul 17 '20 at 16:15
  • 1
    Try turning on all warnings in your compiler. You'd most likely get one for comparing signed and unsigned integers. In gcc you could do this by using `-Wall` flag. – Azad Jul 17 '20 at 16:26
  • IMHO, you set `v.size()` to a const temporary int before the loop. This will prevent the compiler from always evaluating `v.size()`. Yes, some compilers may do this *based on the optimization level and capabilities.* – Thomas Matthews Jul 17 '20 at 17:20
  • FWIW, this is a great example of how to write a question. Your question is specific and clear, you've provided code that demonstrates the problem along with output. Good job. – Caleb Jul 17 '20 at 17:39

1 Answers1

6

You are checking if i is greater than or equal to zero. But since size() returns an unsigned, you are comparing i to an unsigned zero. To do the comparison, i is converted to the same unsigned type that size() returns. Well, every unsigned integral value is greater than or equal to zero. So whatever value i has, the comparison is true.

The size() function has to return a type large enough to hold the maximum number of entries that might be in a vector. That can be larger than the largest value an int can hold. On typical modern platforms, int might be a 32-bit signed type while size() returns a 64-bit unsigned.

Here's the warning from my compiler:

a.cpp: In function ‘int main()’:
a.cpp:10:28: warning: comparison of integer expressions of different signedness:
             ‘int’ and ‘std::vector<int>::size_type’
             {aka ‘long unsigned int’} [-Wsign-compare]
   10 |     for(int i=v.size()-1; i>=(v.size()-k); i--){
      |                           ~^~~~~~~~~~~~~~
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 2
    So the key point here is that `i>=(v.size()-k)` can be seen as `unsigned::operator>=((unsigned) i, (unsigned) 0)`, right? That explains that _`i`is converted to unsigned_ you mention. – rturrado Jul 17 '20 at 16:22
  • 3
    @rturrado It's probably not exactly `unsigned`, but some unsigned type. Perhaps `unsigned long` or whatever `std::size_type` evaluates to on his platform. – David Schwartz Jul 17 '20 at 16:22
  • OK. That was striking for me though. I would have thought `i>=(v.size()-k)` would translate to `int::operator>=((int) i, (int) 0)`. Like, type of `i` is driving the operation. This SO answer states that, in comparisons of signed and unsigned types, signed operands are converted to unsigned: https://stackoverflow.com/a/5416498/260313. – rturrado Jul 17 '20 at 16:27
  • 2
    @rturrado If that happened, then the comparison would get the wrong result if the number of items in the vector was greater than the largest value a signed integer can hold. Consider a vector of bool on a 64-bit platform with 32-bit integers. You can't get around the need for the programmer to consider the types when doing comparisons. (You may be able to get your compiler to give you a warning about mixing signed/unsigned, and you should pay attention to it!) – David Schwartz Jul 17 '20 at 16:49
  • Sorry but I didn't follow you there (first two phrases). In the example provided range for `(unsigned) i` is already much shorter than range for `(long unsigned int) (v.size()-k)`. I don't know if you are talking about that. And to be honest I don't understand either why in a language so strongly typed like C++, you cannot forbid comparisons of different types (i.e. if you want to do them, do a cast). – rturrado Jul 17 '20 at 17:06
  • 1
    @rturrado You can certainly forbid such comparisons if you want. Most compilers will gladly give you an option to generate warnings on such comparisons and an option to treat such warnings as errors. My point is that if it translated both sides to `int` as you suggest, the comparison would give the wrong result if the number of elements in the vector didn't fit in an `int`. – David Schwartz Jul 17 '20 at 18:18
  • OK. Yes, you may be losing information, e.g. casting from 64 bits of a long unsigned int to 32 bits of an int. Thanks so much for your comments! I think I need to read a lot about this yet. Such a small piece of code hiding so many details behind! – rturrado Jul 18 '20 at 19:11