The problem with variable-size integers

C++ guarantees only minimum sizes for integer types - not exact sizes. This creates portability problems when you assume a specific size.

For example, int must be at least 16 bits, but it's typically 32 bits on modern systems. If you assume int is always 32 bits, your program will misbehave on systems where int is actually 16 bits.

Fixed-width integers

To solve this problem, C++11 introduced fixed-width integers - types guaranteed to have the same size on all architectures. They're defined in the <cstdint> header:

Type Size Range
std::int8_t 1 byte signed -128 to 127
std::uint8_t 1 byte unsigned 0 to 255
std::int16_t 2 bytes signed -32,768 to 32,767
std::uint16_t 2 bytes unsigned 0 to 65,535
std::int32_t 4 bytes signed -2,147,483,648 to 2,147,483,647
std::uint32_t 4 bytes unsigned 0 to 4,294,967,295
std::int64_t 8 bytes signed -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
std::uint64_t 8 bytes unsigned 0 to 18,446,744,073,709,551,615

Example:

#include <cstdint>
#include <iostream>

int main()
{
    std::int32_t quantity{32767};  // always 32 bits
    quantity = quantity + 1;        // 32768 always fits
    std::cout << quantity << '\n';

    return 0;
}
Best Practice
Use fixed-width integer types when you need a guaranteed range.

Warning: 8-bit types behave like characters

Due to a C++ specification quirk, most compilers treat std::int8_t and std::uint8_t identically to signed char and unsigned char. This means 8-bit fixed-width types often behave like character types, not integers:

#include <cstdint>
#include <iostream>

int main()
{
    std::int8_t letter{65};
    std::cout << letter << '\n';  // prints 'A', not 65!

    return 0;
}
Warning
The 8-bit fixed-width integer types often behave like characters rather than integers. The 16-bit and larger types don't have this problem.

Best practices for integral types

Given the complexity of integral types, here are the recommended practices:

Use these:

  • int when size doesn't matter (values fit within 16-bit signed integer range)
  • std::int#_t when storing quantities needing guaranteed range
  • std::uint#_t for bit manipulation or well-defined wrap-around behavior

Avoid these:

  • short and long integers (use fixed-width types instead)
  • Unsigned types for quantities (use signed types instead)
  • 8-bit fixed-width types (use 16-bit fixed-width types instead)

What is std::size_t?

The sizeof operator returns a value of type std::size_t. This is an alias for an implementation-defined unsigned integral type used to represent byte-sizes or lengths of objects.

#include <cstddef>
#include <iostream>

int main()
{
    int number{5};
    std::size_t objectSize{sizeof(number)};
    std::cout << objectSize << '\n';  // prints 4 on most systems

    return 0;
}

std::size_t is guaranteed to be unsigned and at least 16 bits. On 32-bit systems it's typically 32 bits; on 64-bit systems it's typically 64 bits.

Best Practice
If you explicitly use std::size_t, include the <cstddef> header. Using sizeof doesn't require a header.