I was recommended to turn on compiler warnings for debugging purposes.
The compiler frequently complained that I was comparing signed and unsigned integers in my loop heads. Thinking it to be better practice to turn all loop counters to size_t, I ran into a problem with a loop that is supposed to decrement to 0 and exit then. As size_t will never become -1 (instead it seems to become the largest possible value), this will turn into an infinite loop. Fortunately, the compiler warned me about this as well.
How can I write the following correctly? for (size_t i = my_vector.size() - 1; i >=0; i -= my_decrement)
for (size_t i = my_vector.size() - 1; i >=0; i -= my_decrement)
I would just use an int rather than a size_t, acknowledge then ignore the warning.
That line of code would fail at the start if my_vector.size() was zero.
Any subtraction with unsigned ints is fraught with danger (including i -= my_decrement) and they stop you relying on the normal laws of arithmetic. Personally, I'm not fond of them.
Yes - there are known 'issues' mixing signed and unsigned.
Since c++20 there is std::ssize() which returns a signed type (ptrdiff_t). But if compiled as 64-bit with int 32 bit there's still a potential issue with assigning a value of type ptrdiff_t to an int.
Consider as C++20:
1 2 3 4 5 6 7 8 9 10 11 12
#include <vector>
#include <iostream>
int main() {
const std::vector nums { 1, 2, 3, 4, 5 };
for (auto i { std::ssize(nums) - 1 }; i >= 0; --i)
std::cout << nums[i] << ' ';
std::cout << '\n';
}
Yes - but the issue here is that .size() returns unsigned type size_t which is 32/64 bit compile aware.
IMO what the standards committee should do is to introduce .ssize() for every instance of where .size() is currently available to return a signed type - and introduce type ssize_t as being the same as ptrdiff_t (which is 64/32 bit aware). Then we can all move forward using nice signed numbers.
But without having .ssize(), then:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <vector>
#include <iostream>
using ssize_t = ptrdiff_t;
int main() {
constexpr ssize_t mydec { 2 };
const std::vector nums { 1, 2, 3, 4, 5 };
for (ssize_t i = std::ssize(nums) - 1; i >= 0; i -= mydec)
std::cout << nums[i] << ' ';
std::cout << '\n';
}
I had to code a simple CRC in java about 3 years ago. The code was really screwy trying to use the required unsigned values in many places, tons of function calls that should have just been logic or arithmetic. You can force it to do it, but yuck. Its a lot better to have access to it directly when you need it -- the code with all that junk is very hard to read, and what did it gain you? You can still make the same mistakes with it, its just now in an ugly wrapper. This is like the C++ style casting argument -- its supposed to be annoying to type and aggravating to read, because WE decided that YOU shouldn't be doing it and it is offered only grudgingly. Or, closer to home, its like making a math tool class in C++ and refusing to use operator overloading so
a = b+c
becomes
a = b.add(c)
and more complicated statements explode into utter garbage.
Thanks for the examples how I could write my loop.
From the discussion, it seems like it is not obvious what "best practice" is in this case.
I believe I will really stick with signed ints for now and, as @lastchance put it,
acknowledge, then ignore the warning.
If the required number of loop iterations gets too large for a signed int, the resulting bug will be easier to spot than a bug resulting from the difference of two unsigned ints that should be negative but instead gets wrapped around to positive values. I do this a couple of times in my code and it would confuse the hell out of me.
The idea of a reverse for loop is easy using numeric indexing or reverse iterators, as others have shown.
It wasn't until C++20 the idea of a C++ stdlib reverse range-based for loop could easily be done, 3rd party libraries such as Boost provided that functionality beforehand, though using one requires including/importing <ranges>: https://www.fluentcpp.com/2020/02/11/reverse-for-loops-in-cpp/
I use range-based for loops all the time when I don't need to worry about indexing or dereferencing a pointer.
C++20 added a nice variant to range-based for loops, init statements. See the cppreference link for details.
Oh yes - particularly if there is mixed signed/unsigned. The issue with a mixed relation is that signed is 'promoted' to unsigned. Even if the signed number is negative, it is 'promoted' to a large unsigned number before the operation (or condition) with the unsigned number is undertaken. This can give rise to many 'wrong' situations.
Consider:
-1 < 0
-1 < 0U
Now even children would probably say correctly that -1 is less than 0. But not in the mad world of unsigned through the looking glass. The first is indeed evaluated as true as expected (both are signed). However in the second, -1 is promoted to unsigned (a large number) as 0U is now unsigned, which is much greater than 0. So the second expression is evaluated as false!