Introduction
This article will cover everything a C++ beginner will ever need to know along with some useful deep dive educational information for some advanced C++ developers. In this article we will cover most C++ data types along with detailed information about the inner workings of this and even some useful checklists for you to know when to use each data type vs another. But that is not all, do you need a reference table of summary data type information well that is also included in this article.
Summary
General Data Types
Data Type | Description | Unsigned / Signed | Size (Bits) | Fixed / Dynamic | Min / Max Range |
---|---|---|---|---|---|
bool |
Boolean value (True / 1, False / 0) | N/A | 8 bits | Fixed | 0 - 1 |
char |
Single ASCII character like 'a'
|
Unsigned & Signed | 8 bits | Fixed | Signed (-128 to 127), Unsigned (0 to 255) |
short |
Smaller integer type | Unsigned & Signed | Usually 16 bits | Dynamic (Platform Dependent) | Signed (-32,768 to 32,767), Unsigned (0 to 65,535) |
int |
Standard integer | Unsigned & Signed | Usually 32 bits | Dynamic (Platform Dependent) | Signed (-2,147,483,648 to 2,147,483,647), Unsigned (0 to 4,294,967,295) |
long |
Larger whole number | Unsigned & Signed | 64 bits (on 64-bit OS), 32 bits (on 32-bit OS) | Dynamic (Platform Dependent) | Signed 32-bit (-2,147,483,648 to 2,147,483,647), Unsigned 32-bit (0 to 4,294,967,295), Signed 64-bit (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807), Unsigned 64-bit (0 to 18,446,744,073,709,551,615) |
long long |
Even larger whole number | Unsigned & Signed | Usually 64 bits | Dynamic (Platform Dependent) | Signed (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807), Unsigned (0 to 18,446,744,073,709,551,615) |
float |
Single precision decimal number | Signed | 32 bits | Fixed | Approx. ±3,402,823,466.3852886 |
double |
Double precision decimal number | Signed | 64 bits | Fixed | Approx. ±1.7976931348623157 × 10³⁰⁸ |
long double (GCC x86/x64) |
Extended precision float on GCC Linux | Signed | Usually 80 bits | Fixed | Approx. ±1.1897314953572317 × 10⁴⁹³² |
long double (Clang macOS) |
Extended precision float on macOS | Signed | 64 bits | Fixed | Approx. ±1.7976931348623157 × 10³⁰⁸ |
long double (GCC PowerPC) |
Extended precision float on PPC | Signed | 128 bits | Fixed | Approx. ±1.1897314953572317 × 10⁴⁹³² |
long double (MSVC / Intel) |
Treated like double
|
Signed | 64 bits | Fixed | Approx. ±1.7976931348623157 × 10³⁰⁸ |
wchar_t |
Wide character (GCC Linux) | Unsigned | 32 bits | Fixed | 0 to 4,294,967,295 |
char16_t |
UTF-16 character | Unsigned | 16 bits | Fixed | 0 to 65,535 |
char32_t |
UTF-32 character | Unsigned | 32 bits | Fixed | 0 to 4,294,967,295 |
void |
No value / function return type | N/A | N/A | N/A | N/A |
Fixed Width Integer Types
Data Type | Description | Signed / Unsigned | Size | Min / Max Range |
---|---|---|---|---|
int8_t |
Signed 8-bit integer | Signed | 8 bits | -128 to 127 |
uint8_t |
Unsigned 8-bit integer | Unsigned | 8 bits | 0 to 255 |
int16_t |
Signed 16-bit integer | Signed | 16 bits | -32,768 to 32,767 |
uint16_t |
Unsigned 16-bit integer | Unsigned | 16 bits | 0 to 65,535 |
int32_t |
Signed 32-bit integer | Signed | 32 bits | -2,147,483,648 to 2,147,483,647 |
uint32_t |
Unsigned 32-bit integer | Unsigned | 32 bits | 0 to 4,294,967,295 |
int64_t |
Signed 64-bit integer | Signed | 64 bits | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
uint64_t |
Unsigned 64-bit integer | Unsigned | 64 bits | 0 to 18,446,744,073,709,551,615 |
intptr_t |
Signed integer to store a pointer | Signed | 64 bits | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
uintptr_t |
Unsigned pointer-sized integer | Unsigned | 64 bits | 0 to 18,446,744,073,709,551,615 |
intmax_t |
Largest available signed integer | Signed | 64 bits | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
uintmax_t |
Largest available unsigned integer | Unsigned | 64 bits | 0 to 18,446,744,073,709,551,615 |
int_least8_t |
Smallest type ≥8 bits (signed) | Signed | 8 bits | -128 to 127 |
int_least16_t |
Smallest type ≥16 bits (signed) | Signed | 16 bits | -32,768 to 32,767 |
int_least32_t |
Smallest type ≥32 bits (signed) | Signed | 32 bits | -2,147,483,648 to 2,147,483,647 |
int_least64_t |
Smallest type ≥64 bits (signed) | Signed | 64 bits | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
int_fast8_t |
Fastest type ≥8 bits (signed) | Signed | 32 bits* | -2,147,483,648 to 2,147,483,647 (platform-dependent) |
int_fast16_t |
Fastest type ≥16 bits (signed) | Signed | 32 bits* | -2,147,483,648 to 2,147,483,647 (platform-dependent) |
int_fast32_t |
Fastest type ≥32 bits (signed) | Signed | 32 bits | -2,147,483,648 to 2,147,483,647 |
int_fast64_t |
Fastest type ≥64 bits (signed) | Signed | 64 bits | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
Inner Workings
Signed VS Unsigned
What Do “Signed” and “Unsigned” Mean?
- Signed integers can represent both negative and positive numbers.
- Unsigned integers can represent only non-negative numbers (zero and positive values), but with double the positive range.
Every integer type in C++ (e.g., char
, short
, int
, long
) can be signed or unsigned:
Type | Signed Range | Unsigned Range |
---|---|---|
int |
−2,147,483,648 to 2,147,483,647 | 0 to 4,294,967,295 |
short |
−32,768 to 32,767 | 0 to 65,535 |
char |
−128 to 127 or 0 to 255 (impl. defined) |
Why Use Unsigned?
- When you never need negative numbers (e.g., sizes, counts, flags)
- To increase the positive range without using a larger type
- To represent raw binary data safely (
uint8_t
,unsigned char
, etc.)
Important Behavior Differences
1. Signed Overflow = Undefined Behavior
int a = INT_MAX;
a += 1; // ❌ Undefined behavior!
2. Unsigned Overflow = Well-defined (Wraparound)
unsigned int b = UINT_MAX;
b += 1; // ✅ Becomes 0 (wraps around)
3. Mixed Arithmetic Can Be Dangerous
unsigned int u = 1;
int s = -2;
if (s < u) // ❌ Might not work as expected!
⚠️ Mixing signed and unsigned values in arithmetic or comparisons can lead to unexpected results due to type promotion rules.
Best Practices
Situation | Recommendation |
---|---|
Array indices, sizes | ✅ Use size_t (unsigned) |
General-purpose arithmetic | ✅ Use int , long , etc. |
Binary buffers, raw bytes | ✅ Use uint8_t , unsigned char
|
Avoiding overflows | ✅ Be explicit with types |
Portability and clarity | ✅ Avoid mixing signed/unsigned types |
Rule of Thumb
Use signed types by default unless there’s a clear reason to use unsigned.
Use unsigned only when negative values are truly invalid, and always guard against wraparound.
What Does Fixed-Width Mean?
- Fixed-width data types have a guaranteed, exact size in bits across all platforms.
- Examples:
-
int8_t
(8 bits) -
uint16_t
(16 bits) -
int32_t
(32 bits) -
uint64_t
(64 bits)
-
These come from the <cstdint>
header and ensure predictable memory usage and range regardless of architecture.
What Does Dynamic-Width Mean?
- Dynamic-width (or implementation-defined width) types have a size that can vary depending on the compiler and platform.
- Examples:
-
int
(commonly 32 bits on modern platforms but not guaranteed) -
long
(typically 32 or 64 bits depending on OS) -
char
(always 8 bits but signedness varies)
-
Their size and range depend on hardware and compiler design.
Comparison Table
Aspect | Fixed-Width Types | Dynamic-Width Types |
---|---|---|
Size Guarantee | Exact number of bits (e.g., 8, 16, 32, 64) | Varies by platform/compiler |
Header | <cstdint> |
Built-in fundamental types |
Portability | High (ideal for cross-platform work) | Medium to low (platform dependent) |
Typical Use Cases | Network protocols, binary files, embedded systems | General programming, system APIs |
Examples |
int32_t , uint64_t
|
int , long , short
|
When to Use Each
Scenario | Recommended Type |
---|---|
Cross-platform binary compatibility | Fixed-width (int32_t , etc.) |
Performance-sensitive code with known hardware | Dynamic-width (int , long ) |
Interfacing with OS or libraries | Match system types (often dynamic) |
Low-level bit manipulation or protocol parsing | Fixed-width for precision |
Why Does This Matter?
- Using fixed-width types avoids bugs caused by size or range differences across platforms.
- Using dynamic-width types can leverage platform-optimized native sizes but risks portability issues.
- For networking, file formats, and embedded systems, fixed-width types are almost always preferred.
Example
#include <cstdint>
int32_t fixedInt = 123456789; // Always 32 bits
int dynamicInt = 123456789; // Size varies by platform (usually 32 bits on desktop)
Summary
- Fixed-width: Exact, predictable sizes, great for portability and binary data.
- Dynamic-width: Compiler-dependent, convenient for general-purpose programming.
What Does Dynamic-Width Mean?
- Dynamic-width (or implementation-defined width) types have a size that can vary depending on the compiler and platform.
- Examples:
-
int
(commonly 32 bits on modern platforms but not guaranteed) -
long
(typically 32 or 64 bits depending on OS) -
char
(always 8 bits but signedness varies)
-
Their size and range depend on hardware and compiler design.
Comparison Table
Aspect | Fixed-Width Types | Dynamic-Width Types |
---|---|---|
Size Guarantee | Exact number of bits (e.g., 8, 16, 32, 64) | Varies by platform/compiler |
Header | <cstdint> |
Built-in fundamental types |
Portability | High (ideal for cross-platform work) | Medium to low (platform dependent) |
Typical Use Cases | Network protocols, binary files, embedded systems | General programming, system APIs |
Examples |
int32_t , uint64_t
|
int , long , short
|
When to Use Each
Scenario | Recommended Type |
---|---|
Cross-platform binary compatibility | Fixed-width (int32_t , etc.) |
Performance-sensitive code with known hardware | Dynamic-width (int , long ) |
Interfacing with OS or libraries | Match system types (often dynamic) |
Low-level bit manipulation or protocol parsing | Fixed-width for precision |
Why Does This Matter?
- Using fixed-width types avoids bugs caused by size or range differences across platforms.
- Using dynamic-width types can leverage platform-optimized native sizes but risks portability issues.
- For networking, file formats, and embedded systems, fixed-width types are almost always preferred.
Example
#include <cstdint>
int32_t fixedInt = 123456789; // Always 32 bits
int dynamicInt = 123456789; // Size varies by platform (usually 32 bits on desktop)
Summary
- Fixed-width: Exact, predictable sizes, great for portability and binary data.
- Dynamic-width: Compiler-dependent, convenient for general-purpose programming.
Min Bits vs Speed Fixed Data Type Requirements
Understanding int_leastN_t
vs int_fastN_t
When writing portable and efficient C++ code using fixed-width integers, you may come across two special families of types defined in <cstdint>
:
-
int_leastN_t
/uint_leastN_t
-
int_fastN_t
/uint_fastN_t
These are designed to give you more control over performance or memory usage than plain types like int8_t
.
int_leastN_t
— Least Width, Guaranteed Minimum Size
Purpose:
Use the smallest integer type that is at least N bits wide.
Example:
int_least16_t x = 1000; // At least 16 bits, but possibly wider
When to use it:
- Memory-constrained systems (e.g., microcontrollers)
- You don’t care about performance, just minimal size and compatibility
Guarantee:
It will never be narrower than N bits, but could be wider depending on the platform.
int_fastN_t
— Fastest Type with Minimum Width
Purpose:
Use the fastest integer type that is at least N bits wide.
Example:
int_fast16_t y = 1000; // May be 32-bit or even 64-bit if it's faster
When to use it:
- Performance-critical code
- Tight loops, counters, array indexing
- You don’t care about memory size, just speed
Guarantee:
Like int_leastN_t
, it’s at least N bits, but chosen by the compiler for best performance.
Quick Comparison
Feature | int_leastN_t |
int_fastN_t |
---|---|---|
Minimum Size | Yes | Yes |
Memory Efficient | ✅ Best | ❌ Not always |
Performance Focused | ❌ No | ✅ Yes |
Platform Variance | Low | High (optimized by compiler) |
Use Case | Portability & Size | Speed & Performance |
Why These Exist
C++ runs on many different hardware architectures. What’s fast and optimal on one platform might be inefficient on another. These types:
- Help write portable, optimized, and safe code.
- Let the compiler decide what’s best, based on your priority: speed or size.
Tip
If you need exact bit-width, use intN_t
(e.g. int8_t
, int64_t
).
If you're writing generic code, and care about size or speed more than exact bits, use int_leastN_t
or int_fastN_t
.
Min Bits vs Speed Fixed Data Type Requirements
Understanding int_leastN_t
vs int_fastN_t
When writing portable and efficient C++ code using fixed-width integers, you may come across two special families of types defined in <cstdint>
:
-
int_leastN_t
/uint_leastN_t
-
int_fastN_t
/uint_fastN_t
These are designed to give you more control over performance or memory usage than plain types like int8_t
.
int_leastN_t
— Least Width, Guaranteed Minimum Size
Purpose:
Use the smallest integer type that is at least N bits wide.
Example:
int_least16_t x = 1000; // At least 16 bits, but possibly wider
When to use it:
- Memory-constrained systems (e.g., microcontrollers)
- You don’t care about performance, just minimal size and compatibility
Guarantee:
It will never be narrower than N bits, but could be wider depending on the platform.
int_fastN_t
— Fastest Type with Minimum Width
Purpose:
Use the fastest integer type that is at least N bits wide.
Example:
int_fast16_t y = 1000; // May be 32-bit or even 64-bit if it's faster
When to use it:
- Performance-critical code
- Tight loops, counters, array indexing
- You don’t care about memory size, just speed
Guarantee:
Like int_leastN_t
, it’s at least N bits, but chosen by the compiler for best performance.
Quick Comparison
Feature | int_leastN_t |
int_fastN_t |
---|---|---|
Minimum Size | Yes | Yes |
Memory Efficient | ✅ Best | ❌ Not always |
Performance Focused | ❌ No | ✅ Yes |
Platform Variance | Low | High (optimized by compiler) |
Use Case | Portability & Size | Speed & Performance |
Why These Exist
C++ runs on many different hardware architectures. What’s fast and optimal on one platform might be inefficient on another. These types:
- Help write portable, optimized, and safe code.
- Let the compiler decide what’s best, based on your priority: speed or size.
Tip
If you need exact bit-width, use intN_t
(e.g. int8_t
, int64_t
).
If you're writing generic code, and care about size or speed more than exact bits, use int_leastN_t
or int_fastN_t
.
Usage
Syntax
#include <cstdint> // for fixed-width and related types
#include <cstddef> // for intptr_t, uintptr_t
#include <iostream>
int main() {
// Fixed-width integer types
int8_t a_int8 = -100;
uint8_t a_uint8 = 200;
int16_t a_int16 = -30000;
uint16_t a_uint16 = 60000;
int32_t a_int32 = -2000000000;
uint32_t a_uint32 = 4000000000U;
int64_t a_int64 = -9000000000000000000LL;
uint64_t a_uint64 = 18000000000000000000ULL;
// Pointer-sized integers
intptr_t a_intptr = 0;
uintptr_t a_uintptr = 0;
// Maximum-width integers
intmax_t a_intmax = -9223372036854775807LL;
uintmax_t a_uintmax = 18446744073709551615ULL;
// Least-width integers
int_least8_t a_least8 = -120;
int_least16_t a_least16 = -32000;
int_least32_t a_least32 = -2000000000;
int_least64_t a_least64 = -9000000000000000000LL;
// Fastest minimum-width integers
int_fast8_t a_fast8 = -120;
int_fast16_t a_fast16 = -32000;
int_fast32_t a_fast32 = -2000000000;
int_fast64_t a_fast64 = -9000000000000000000LL;
// Fundamental types
bool a_bool = true;
char a_char = 'A';
short a_short = -32000;
int a_int = 1000000;
long a_long = 1000000000L;
long long a_longlong = 1000000000000LL;
float a_float = 3.14f;
double a_double = 3.1415926535;
long double a_longdouble = 3.141592653589793238462643383279L;
wchar_t a_wchar = L'Ω';
char16_t a_char16 = u'A';
char32_t a_char32 = U'😊';
// Void pointer example (cannot declare a variable of type void)
void* a_voidptr = nullptr;
// Output some values to prevent unused variable warnings
std::cout << "int8_t: " << +a_int8 << "\n"
<< "uint8_t: " << +a_uint8 << "\n"
<< "int16_t: " << a_int16 << "\n"
<< "uint16_t: " << a_uint16 << "\n"
<< "int32_t: " << a_int32 << "\n"
<< "uint32_t: " << a_uint32 << "\n"
<< "int64_t: " << a_int64 << "\n"
<< "uint64_t: " << a_uint64 << "\n"
<< "bool: " << a_bool << "\n"
<< "char: " << a_char << "\n"
<< "wchar_t: " << a_wchar << "\n"
<< "char16_t: " << static_cast<char>(a_char16) << "\n"
<< "char32_t: " << static_cast<char>(a_char32) << "\n"
<< "float: " << a_float << "\n"
<< "double: " << a_double << "\n"
<< "long double: " << a_longdouble << "\n";
return 0;
}
Notes
-
+a_int8
and+a_uint8
are used when printinguint8_t
/int8_t
becausechar
types print as characters by default. -
void
itself can’t be instantiated; only pointers tovoid
(void*
) are valid. -
char16_t
andchar32_t
values are cast when printing, because their types don’t directly stream tostd::cout
. - You can modify or extend the example for your testing or documentation.
Usage Rules
Introduction
This section will provide you with a list of yes or no questions to determined which data type you need to use to store your data in c++.
Decision Tree
Q01 - Which Data do you need to store?
Text - Char
Whole Number - Integer
Decimal Number - Float / Double
Boolean True / False - Bool (END)
Pointer / Memory Address - intptr_t / uintptr_t
Q02 - Positive or Negative Values?
Positive - Unsigned
Negative / Both - Signed
Q04 - What size values do you need to store?
Whole & Decimal Numbers:
(MIN) 128 / 0 - (MAX) 127 / 255 - 8 Bits
(MIN) -32,768 / 0 - (MAX) 32,767 / 65,535 - **16 Bits
(MIN) -2,147,483,648 / 0 - (MAX) 2,147,483,647 / 4,294,967,295 - 17 Decimal Digits Precision - 32 Bits
(MIN) -9,223,372,036,854,775,808 / 0 - (MAX) 9,223,372,036,854,775,807 / 18,446,744,073,709,551,615 - 15 decimal digits precision - 64 Bits
Whole Numbers Only:
Maximum Bits Available - 64 or More Bits
Characters:
UTF-8 - 8 Bits
UTF-16 - 16 Bits
UTF-32 - 32 Bits
Q03 - Do you have requirements?
Speed? (Fast)
Minimum Number of Bits? (8, 16, 32, 64)
Q05 - Fixed vs Dynamic Data Sizes
Performance, Portability, Use Cases (General-purpose apps, UI, platform-specific code) - Dynamic
Memory Efficiency, Safer, Better Optimization, Use Cases (Embedded systems, network protocols, file formats, security-critical code) - Fixed
Answer Table
Data Type | Signed / Unsigned | Fixed / Dynamic | Size (Bits) |
---|---|---|---|
bool |
N/A | Dynamic | 8 bits |
signed char |
Signed | Dynamic | 8 bits |
unsigned char |
Unsigned | Dynamic | 8 bits |
short |
Signed | Dynamic | 16 bits |
unsigned short |
Unsigned | Dynamic | 16 bits |
int |
Signed | Dynamic | 32 bits (commonly) |
unsigned int |
Unsigned | Dynamic | 32 bits (commonly) |
long |
Signed | Dynamic | 32 bits (Windows), 64 bits (Unix) |
unsigned long |
Unsigned | Dynamic | 32 bits (Windows), 64 bits (Unix) |
long long |
Signed | Dynamic | 64 bits |
unsigned long long |
Unsigned | Dynamic | 64 bits |
int8_t |
Signed | Fixed | 8 bits |
uint8_t |
Unsigned | Fixed | 8 bits |
int16_t |
Signed | Fixed | 16 bits |
uint16_t |
Unsigned | Fixed | 16 bits |
int32_t |
Signed | Fixed | 32 bits |
uint32_t |
Unsigned | Fixed | 32 bits |
int64_t |
Signed | Fixed | 64 bits |
uint64_t |
Unsigned | Fixed | 64 bits |
intptr_t |
Signed | Fixed | Platform-dependent: 32 bits or 64 bits |
uintptr_t |
Unsigned | Fixed | Platform-dependent: 32 bits or 64 bits |
intmax_t |
Signed | Fixed | Maximum width supported (≥64 bits) |
uintmax_t |
Unsigned | Fixed | Maximum width supported (≥64 bits) |
int_least8_t / int_fast8_t
|
Signed | Fixed | ≥8 bits |
uint_least8_t / uint_fast8_t
|
Unsigned | Fixed | ≥8 bits |
int_least16_t / int_fast16_t
|
Signed | Fixed | ≥16 bits |
uint_least16_t / uint_fast16_t
|
Unsigned | Fixed | ≥16 bits |
int_least32_t / int_fast32_t
|
Signed | Fixed | ≥32 bits |
uint_least32_t / uint_fast32_t
|
Unsigned | Fixed | ≥32 bits |
int_least64_t / int_fast64_t
|
Signed | Fixed | ≥64 bits |
uint_least64_t / uint_fast64_t
|
Unsigned | Fixed | ≥64 bits |
void |
N/A | N/A | N/A |
Top comments (0)