I have a better solution: address the root cause of unsafe semantics by not using raw indexed for-loops, unless one absolutely needs an index, in which case one should generate it with std::views::enumerate. To reverse it, use std::views::enumerate | std::views::reverse. Ditto for languages with similar semantics.

I almost never write a raw for-i loop any more, especially since 99% of the time I want to enumerate through the entire array or vector, and I can just use a ranged for-loop to do that. It allows me to redesign my code around the data, express things at a higher level, and my code looks far more SIMDable and reminiscent of array programming languages. And yet it is safer, I will never see under/overflow or any of these old-hat problems.

If you are using C, then too bad, you're stuck with a language that doesn't allow the programmer to more meaningfully and more clearly express intent at a higher level of abstraction without paying additional runtime costs.

This stuff compiles to broadly the same assembly.

> I have a better solution: address the root cause of unsafe semantics by not using raw indexed for-loops, unless one absolutely needs an index, in which case one should generate it with std::views::enumerate.

That’s why Swift completely removed that kind of for loop (https://github.com/swiftlang/swift-evolution/blob/main/propo...)

> This stuff compiles to broadly the same assembly.

At the cost of requiring a more complex compiler. Also, C/C++ won’t find “broadly the same” sufficient. They’ll want to see the same performance.

Loop counters really are not the problem. The problem is that even when using unsigned integers you often want to do 'signed math' on them (e.g. adding a negative amount, or you could have an expression made entirely of unsigned integers (like ((x - y) + z) where an intermediate result may become negative even when the end result is positive - and in languages with overflow check that may result in a panic).

A better rule of thumb is to always use signed integers, except for bit twiddling and modulo-math.

Especially with 64-bit integers it's really no longer an issue to lose one bit for the sign (63 bits ought to be enough for anybody heh).

But are we necessarily limited to native integer types? At least with C++, the type system is powerful enough to support integer replacement types (eg. [0][1]) that don't inherit these issues. (Where, for example, the subtraction of an unsigned from another unsigned returns a value of signed integer type.) Another advantage being the ability to customize the overflow/underflow handling policy per declaration (rather than relying on a compiler flag that applies globally).

[0] https://www.boost.org/doc/libs/develop/libs/safe_numerics/do...

[1] https://github.com/duneroadrunner/SaferCPlusPlus/blob/master...

I would rather do away with signed vs unsigned integer types completely, and instead have sign-agnostic integers like down on the assembly level (the world has settled on two's-complement anyway). Signed-vs-unsigned only matters in one situation: when extending a narrow integer type to a wider integer type (e.g. "sign-extension"), and this could be an explicit operation.

Beyond that, the decision whether a number is signed or unsigned is only needed for string formatting (e.g. it's like Schroedingers integers: whether a number is signed or unsigned only matters when you actually look at the number).

Wait till this guy learns about multiplication and division

Yeah ok got me, but AFAIK at least multiplication is sign-agnostic when the result is clamped to the width of the inputs (e.g. the result of multiplying two 64-bit values being clamped to 64-bits). Not sure about division though.

But in any case: with 'sign-agnostic' integer types high level languages would simply need separate signed vs unsigned mul/div operators. Not a big thing when modern languages already have different operators for wraparound vs overflow-checked arithmetic (for instance + vs +% in Zig).

[deleted]