Stroustrup recommends int over unsigned. Dijkstra recommends int over unsigned. Google coding guidelines recommend int over unsigned.
Blogger recommends unsigned over int.
Tough choice.
Stroustrup recommends int over unsigned. Dijkstra recommends int over unsigned. Google coding guidelines recommend int over unsigned.
Blogger recommends unsigned over int.
Tough choice.
I liked how the discussion of 'delta = x - y' moved right on to how really you usually want delta = abs(x - y), so let's talk about that instead...
Even beyond Stroustrup, Dijkstra, and Google, this whole panel of C++ luminaries agrees to prefer signed types and explains pretty clearly why:
- 12:12-13:08 - https://www.youtube.com/watch?v=Puio5dly9N8#t=12m12s
- 42:40-45:26 - https://www.youtube.com/watch?v=Puio5dly9N8#t=42m40s
- 1:02:50-1:03:15 - https://www.youtube.com/watch?v=Puio5dly9N8#t=1h2m50s
Thanks for the excerpts! I was trying to understand the reasoning, which seem to just be in the 2nd excerpt:
- The rules of signed/unsigned are complicated and there is too much auto-conversion - does that mean languages that make this more explicit means this is fine? It just seems ideal to have stronger typing. - It is mentioned that you can initialized an unsigned int to "-2" - but that presumably could also be fixed in the language.
I'm trying to separate out which is "don't do this in C/C++" and which is "don't do this in any language".
> I'm trying to separate out which is "don't do this in C/C++" and which is "don't do this in any language".
To achieve high performance, any language would need to implement integer addition with a single machine instruction like 'ADD'. Languages can achieve more intuitive behavior by adding an operand check before the 'ADD', or by using an 'ADC' instruction and checking the carry bit afterwards. But adding branch statements to every add operation would slow computation significantly. Clever languages/compilers might deduce certain invariants and variable ranges that enable it to remove some of these branches to help in certain special cases.
Evidently Rust has an optional "safe add" that adds the branches to check for overflow. So newer languages offer more explicit options. But the core issue is more fundamental than language-specific.
that argument (and many others) also round to essentially "implicit imprecise integer casting is surprising but we do it anyway" which is... perhaps the actual problem???
I've made lints for Go that simply disallow implicit number casting, and omfg the (real, occurring but unnoticed) bugs it found. those kinds of lints are trivial to build, you can just stop doing it. forcing visible casts made many of these problematic patterns extremely suspicious at a glance, catching issues at review time far more easily.
I believe there's ways to configure clang to flag dangerous implicit casts as well.
-Wconversion perhaps: https://clang.llvm.org/docs/DiagnosticsReference.html#wconve... or -Wimplicit-int-conversion for the main check I've built in other languages (afaict, I have not used C(++) professionally)
I still don't like having explicit conversions everywhere like in rust. Either you're not thinking too hard about it and the explicit conversions are not really doing anything for you, or you are, meaning you need to be reasoning about it every time and justifying why it can never fail and/or injecting error handling. I would be a much happier rust user if index/length types were are i64 and we relegated unsigned types to serialization almost exclusively. I have other gripes for unsigned types btw, those are just my complaints why explicit casts are not a panacea.
Considering Rust doesn’t have such nonsense, I’m inclined C/C++ have utterly broken generations of programmers.
In what world is using a signed value to index a normal array a good idea?
Makes for horrible footguns like:
history[counter % SIZE] = …
(One cursed day counter rolls over, becomes negative, and an out-of-bounds write occurs)
Everything went South as soon as we broke the abstraction of arrays and treated them as pointers.
Commenters here are pretty much arguing which way to hold scissors while running instead of realizing that one shouldn’t do that in the first place…
> Makes for horrible footguns like:
> history[counter % SIZE] = …
The footgun here is that the “modulo” operator does not actually calculate the modulo in C. In Python, this works correctly for negative values.
> In Python, this works correctly for negative values.
They’re both “broken” in different ways. Arguably C’s brokenness is more apparent and less useful but Python also has footguns: C uses truncated division for its “modulo” so the remainder has the sign of the dividend, Python uses floored division so the remainder has the sign of the divisor instead.
The wiki page for modulo has a pretty extensive page on the subject.
I say you want a pair of operations such that (a, b) = quotient_and_remainder(x, y) gives you a and b such that a * y + b = x
The Euclidean division and remainder work, the other division and remainder also work, and they're both identical for the positive integers so people who only think about the positive integers won't even notice there's a choice here. So I like that Rust provides both pairs, in the same way Rust provides both Wrapping<T> and Saturating<T> because maybe you mean wrapping overflow or maybe you mean saturating overflow and we should make you choose not just assume we know best.
> Python uses floored division so the remainder has the sign of the divisor instead
Serious question, how is that a footgun? In decades of software development I have never needed a negative divisor for modulo. What would you use it for?
Commenter implies authority dictates strategy.
We should be engaging with the article's content.
Actually, in that case, no.
Blogger hasn't bothered to refer to those well known and detailed opinions, from very experienced authorities, and provide detailed rebuttal to those authorities claims, so his opinion can be safely ignored.
Commenter comments before reading.
The entire article literally starts by referring to Google coding guidelines and other well known opinions as arguments against unsigned, then tries to provide a rebuttal.
ClickHouse code style recommends unsigned in every case when you don't need the sign: https://clickhouse.com/docs/development/style
No. It says the reverse: "11. unsigned. Use unsigned if necessary."
Unsigned is necessary only if you're working with bit fields, bit masks or require explicit modulo n-bit arithmetic.
For all other cases, use signed
Gosling went one step further unsigned isn't even available, although nowadays there are helper classes for doing unsigned arithmetic.
signed overflow (or underflow) is frequently undefined behavior. (often because it's undefined in C)
unsigned is frequently defined. (often because it's defined in C)
tough choice.
(honestly I just lean towards "over/underflow should raise unless explicitly allowed", the ratio of unintended to intended-and-fully-checked overflow behavior is almost certainly FAR beyond 100:1)
Of course unsigned is defined. That's besides the point. The point is: how often in your code, do you expect 1 minus 2 to equal a very large number, vs. the number -1.
both seem equally undesirable to me in all cases where I intend neither. though one also risks undefined behavior, so that is strictly worse.
the reason I use a type system is to make error classes unrepresentable (where possible) or a failure. these are both leaky abstractions in the worst possible manifestation: silent misbehavior at runtime.
I honestly feel like it is the point at which you find field arithmetic intuitive that demonstrates you have finally understood computers.