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
resultis 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
rafterr--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.ris of typesize_t, an implementation-defined unsigned integer type.-1is an expression of typeint.On most systems,
size_tis at least as wide asint. On such systems, the integral promotions cause the value ofreither to be converted tounsigned intor to keep its existing type (the former can happen ifsize_thas 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_tis 16 bits (let's say it's atypedefforunsigned short) andintis 32 bits. SoSIZE_MAX == 65535andINT_MAX == 2147483647. Or we could have a 32-bitsize_tand 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_tand value65535. Since signedintcan represent all the values of typesize_t, the integral promotions convert the value to65535of 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, ifris declared as anunsigned short, or anunsigned char, or even a plaincharon a system where that type is signed, the value ofresultwill probably befalse. (I say probably becauseshortor evenunsigned charcan have the same width asint, in which caseresultwill 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_t18.2 paragraphs 6-7:
So there's no prohibition on making
size_tnarrower thanint. I can almost plausibly imagine a system whereintis 64 bits, but no single object can be bigger than 232-1 bytes sosize_tis 32 bits.