Consider the following†:
size_t r = 0;
r--;
const bool result = (r == -1);
Does the comparison whose result initialises result
have well-defined behaviour?
And is its result true
, as I'd expect?
This Q&A was written because I was unsure of two factors in particular.
They may both be identified by use of the term "crucial[ly]" in my answer.
† This example is inspired by an approach for loop conditions when the counter is unsigned:
for (size_t r = m.size() - 1; r != -1; r--)
Strictly speaking, the value of
result
is implementation-defined. In practice, it's almost certain to betrue
; I'd be surprised if there were an implementation where it'sfalse
.The value of
r
afterr--
is the value ofSIZE_MAX
, a macro defined in<stddef.h>
/<cstddef>
.For the comparison
r == -1
, the usual arithmetic conversions are performed on both operands. The first step in the usual arithmetic conversions is to apply the integral promotions to both operands.r
is of typesize_t
, an implementation-defined unsigned integer type.-1
is an expression of typeint
.On most systems,
size_t
is at least as wide asint
. On such systems, the integral promotions cause the value ofr
either to be converted tounsigned int
or to keep its existing type (the former can happen ifsize_t
has the same width asint
, but a lower conversion rank). Now the left operand (which is unsigned) has at least the rank of the right operand (which is signed). The right operand is converted to the type of the left operand. This conversion yields the same value asr
, and so the equality comparison yieldstrue
.That's the "normal" case.
Suppose we have an implementation where
size_t
is 16 bits (let's say it's atypedef
forunsigned short
) andint
is 32 bits. SoSIZE_MAX == 65535
andINT_MAX == 2147483647
. Or we could have a 32-bitsize_t
and a 64-bitint
. I doubt that any such implementation exists, but nothing in the standard forbids it (see below).Now the left side of the comparison has type
size_t
and value65535
. Since signedint
can represent all the values of typesize_t
, the integral promotions convert the value to65535
of typeint
. Both side of the==
operator have typeint
, so the usual arithmetic conversions have nothing to do. The expression is equivalent to65535 == -1
, which is clearlyfalse
.As I mentioned, this kind of thing is unlikely to happen with an expression of type
size_t
-- but it can easily happen with narrower unsigned types. For example, ifr
is declared as anunsigned short
, or anunsigned char
, or even a plainchar
on a system where that type is signed, the value ofresult
will probably befalse
. (I say probably becauseshort
or evenunsigned char
can have the same width asint
, in which caseresult
will betrue
.)In practice, you can avoid the potential problem by doing the conversion explicitly rather than relying on the implementation-defined usual arithmetic conversions:
or
C++11 standard references:
size_t
18.2 paragraphs 6-7:
So there's no prohibition on making
size_t
narrower thanint
. I can almost plausibly imagine a system whereint
is 64 bits, but no single object can be bigger than 232-1 bytes sosize_t
is 32 bits.