So, stripping down your code to a minimal example, you're asking why this prints 0
:
#include <iostream>
#include <string>
int main()
{
int a = -1;
std::string::size_type b = 3;
int c = a % b;
std::cout << c << '\n';
}
The primary operation in question here is this:
a % b
Per the standard,
5.6 Multiplicative operators [expr.mul]
- The operands of * and / shall have arithmetic or unscoped enumeration
type; the operands of % shall have integral or unscoped enumeration
type. The usual arithmetic conversions are performed on the operands
and determine the type of the result.
So.. what about those "usual arithmetic conversions"? This is to mate the types of the two operands to a common type prior to performing the actual operation. The following are considered in order :
- If both operands are integers, integer promotion is first performed on both operands. If after integer promotion the operands still have different types, conversion continues as follows:
- If one operand has an unsigned type T whose conversion rank is at least as high as that of the other operand’s type, then the other operand is converted to type T.
- Otherwise, one operand has a signed type T whose conversion rank is higher than that of the other operand’s type. The other operand is converted to type T only if type T is capable of representing all values of its previous type.
- Otherwise, both operands are converted to the unsigned type that corresponds to the signed type T.
That's a lot of legalize for what effectively says this:
- You have two operands, a
signed int
and a std::string::size_type
- The rank of
std::string::size_type
is greater than that of signed int
- Therefore, the
signed int
operand is converted to type std::string:size_type
prior to the operation being requested.
So all that is left is the conversion, to wit, there is one more piece of legalize:
4.7 Integral conversions [conv.integral]
- If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n
where n is the number of bits used to represent the unsigned type).
[Note: In a two’s complement representation, this conversion is
conceptual and there is no change in the bit pattern (if there is no
truncation). —end note]
That means, on a 32-bit std::string::size_type
platform, you're going to get 232-1 as the converted value from int
(-1).
Which means...
4294967295 % 3
Which is... zero. If std::string::size_type
is 64-bits, then everything above stays the same, save for the final calculation, which would be:
18446744073709551615 % 3
static_cast<size_t>(-1)
is. Then calculate its remainder when divided by 3. Spoiler alert: it's 0. – Huckaby