Gerhard Fiedler said:
I don't know whether it is /the/ real issue, but IMHO (I rarely use the
"H", but here I do
it is an important issue.
IMO it is only useful in places where modulo arithmetic is purposefully
used. In highly optimized code for address (and other) calculations,
for
example. In encryption and hashing algorithms. There probably are
others; I'm by no means an expert here.
OK, so addresses and such where modulo arithmetic is a boon (I use that
OPTIMIZATION a lot too), there, modular integers are a great fit
(sometimes). Modular integers are a misfit for loop counters, but so are
signed integers. So the issue is that there is a missing integer type:
unsigned with overflow checking (and it would be nice if signed did
overflow checking too). Hence, everyone HAS to SHOEHORN a square peg in a
round hole: signed or modular integer where something else would be more
appropriate. I get it now.
As I see it, in the dawn of the language everything was clear: you used
(signed) int for everything -- except when you needed modulo
arithmetic,
which was when you used unsigned int. (Note also that "unsigned"
doesn't
mean "not negative" -- it means "has no concept of sign", neither
positive nor negative. Another thing often overlooked.)
I see no evidence that it was not just an error of omission or
obliviousness to requirements or inadvertently it just happened to work
out that way. Then again, I haven't analyzed all the permeating affects
of "doing it right". "doing it right" meaning regarding modular integers
as a special optimized type to be used where modulo arithmetic is highly
advantagous.
In the library things were less clear: since there was no (signed) int
to represent the whole range of addressable bytes,
they sort of /had/ to
use unsigned for size_t. And bingo -- we were in the mess that we still
are and are compelled to use (unsigned) size_t for calculations that
are
not meant to be modulo arithmetic.
But that is still the wrong line of reasoning. You are suggesting that
signed is the right thing because modular is the wrong thing, and that is
not the case. The right thing is non-existent in the language. Error of
omission.
So there are two orthogonal concepts: modulo arithmetic (the language
says unsigned is modulo, signed is not necessarily) and whether a given
variable may or may not assume negative values.
Well there are more than 2 concepts: signed, unsigned, the modulo
optimization, overflow checking. 4 concepts and the combinations of them.
Unluckily these two are
all too often mixed up in the discussion -- and they are mixed up
between the language itself (that makes the clear distinction that
unsigned is for modulo arithmetic and signed is not) and the library
(which uses unsigned for places where modulo arithmetic is not
necessarily desired).
Since all this goes way back to early C (and early C++), I don't see
any
of this change any time soon (or not so soon). So we just have to live
with the mess and find our way around it.
I foresee new integer types in the standard soon.
It can't be "clean" code,
because we have to live with the library and its compromise (which on
some platforms is a necessary compromise), so we have the various
opinions on how to make the code "less unclean".
There's only one good solution going forward. Gotta start biting the
bullet.
The implicit conversions between signed and unsigned didn't help,
either.
Did I say FOUR concepts and their conbinations? My bad. FIVE:
conversions!
If I had a compiler with a switch that disabled them, I'd have
this switch on all the time.
Hear, hear! Nothing a wrapper won't solve though in the interim or to use
in debug builds.
There should be a signed_cast (similar to
const_cast) for these cases. Luckily, most modern compilers are
starting
to see the problem and introducing warnings for these cases -- almost
as
good.
IMO the "half-bakedness" came in when the library used unsigned in ways
the language definition didn't really support -- for practical reasons
(as so much in C and C++).
I think it happened before that during "requirements analysis" (not that
that was formally, thoroughly or capably don e though, I don't know if it
was or not) when the error of omission occurred.
Because of backwards compatibility (another
common reason for how things are in C and C++), we're stuck with this
ever since.
Stuck with the workarounds. C++'s abstraction capabilities alleviate the
issue somewhat if taking advantage of them.
Also, the situation with the typically highly optimized code in
relatively slow 8- and 16-bit environments (when all this was defined)
is quite different from how it is now. So what made good sense back
then
doesn't seem to make as much sense now
In this case, I'm not sure it made sense back then either.
-- but the rules from back then
are still valid.
Not really. Being "lesser wrong" is still "wrong".