Skip to content

Variables and Data Types — Primitives, Auto, Type Deduction, and sizeof

DodaTech Updated 2026-06-28 9 min read

In this tutorial, you will learn about Variables and Data Types. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ variables must be declared with a type before use, but modern C++ offers auto for type deduction while primitive types have platform-dependent sizes that sizeof can reveal.

What You'll Learn

You will master C++ primitive types (int, char, bool, float, double, void), understand signed versus unsigned integers and their pitfalls, use auto for type deduction, measure type sizes with sizeof, discover minimum and maximum values with <limits>, and avoid common type conversion errors.

Why It Matters

Every value in a C++ program occupies memory of a specific size and layout. Unlike dynamically-typed languages where variables can change type freely, C++ determines the type of every variable at compile time. This static typing catches entire categories of bugs before your program runs. Understanding types is foundational to writing correct, efficient C++.

Learning Path

graph LR
    A["03: Hello World"] --> B["04: Variables & Types"]
    B --> C["05: Constants & Modifiers"]
    C --> D["06: Operators"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

Fundamental Types

C++ provides a set of fundamental types that map directly to hardware capabilities:

Type Typical Size Range Notes
bool 1 byte true or false Stored as integer 0/1
char 1 byte -128 to 127 or 0-255 Implementation-defined signedness
signed char 1 byte -128 to 127
unsigned char 1 byte 0 to 255
short 2 bytes -32,768 to 32,767
unsigned short 2 bytes 0 to 65,535
int 4 bytes ~-2.1B to ~2.1B Typical, not guaranteed
unsigned int 4 bytes 0 to ~4.2B
long 4 or 8 bytes Platform-dependent Same as int on 32-bit
long long 8 bytes -9E18 to 9E18 At least 64 bits (C++11)
float 4 bytes ~7 decimal digits IEEE 754 single precision
double 8 bytes ~15 decimal digits IEEE 754 double precision
long double 8/10/16 bytes Platform-dependent Extended precision

Variable Declaration and Initialization

#include <iostream>

int main() {
    int x;               // default-initialized (garbage value)
    int y = 42;          // copy-initialization
    int z(42);           // direct-initialization
    int w{42};           // brace-initialization (C++11, preferred)
    int v{};             // value-initialization (zero)
    
    std::cout << y << " " << w << " " << v << "\n";
}

Expected output:

42 42 0

Brace initialization (using {}) is the recommended style in modern C++. It prevents narrowing conversions (e.g., int x{3.14}; would be a compilation error). The other forms silently truncate.

Type Deduction with auto

The auto keyword tells the compiler to deduce the type from the initializer:

#include <iostream>
#include <typeinfo>

int main() {
    auto a = 42;          // int
    auto b = 3.14;        // double
    auto c = 3.14f;       // float
    auto d = 'A';         // char
    auto e = true;        // bool
    auto f = 42ULL;       // unsigned long long
    
    std::cout << typeid(a).name() << "\n";
}

Use auto to avoid writing long type names and to ensure consistency when types change. However, auto is not magic: it strips references and const by default unless you add & or const:

int x = 42;
int& ref = x;
auto a = ref;       // a is int, not int&
auto& b = ref;      // b is int&
const int c = 10;
auto d = c;         // d is int, not const int
const auto e = c;   // e is const int

Signed vs Unsigned

Signed types can represent negative and positive values using two's complement. Unsigned types cannot represent negatives but double the positive range.

#include <iostream>

int main() {
    unsigned int u = 0;
    u = u - 1;  // wraparound to 4294967295 (on 32-bit)
    std::cout << u << "\n";
    
    int s = 10;
    unsigned int t = 5;
    auto result = s - t;  // s promoted to unsigned, result is unsigned!
    std::cout << result << "\n";  // prints 5
    
    // Dangerous:
    std::cout << (s < t - 10) << "\n";  // t - 10 wraps to huge value
}

Rule: Avoid mixing signed and unsigned in comparisons or arithmetic. The compiler promotes the signed value to unsigned, often causing surprising behavior.

Fixed-Width Integer Types

The <cstdint> header provides types with guaranteed sizes:

#include <iostream>
#include <cstdint>

int main() {
    int8_t       i8;    // exactly 8 bits, signed
    uint16_t     u16;   // exactly 16 bits, unsigned
    int32_t      i32;   // exactly 32 bits, signed
    uint64_t     u64;   // exactly 64 bits, unsigned
    
    int_least32_t li32;  // at least 32 bits
    int_fast32_t  fi32;  // fastest for 32-bit ops
    
    std::cout << sizeof(int64_t) << "\n";
}

Prefer these fixed-width types when you need exact sizes (networking, binary formats, Embedded Systems).

The sizeof Operator

sizeof returns the size of a type or object in bytes:

#include <iostream>

int main() {
    std::cout << "int: " << sizeof(int) << "\n";
    std::cout << "double: " << sizeof(double) << "\n";
    std::cout << "bool: " << sizeof(bool) << "\n";
    
    int arr[10];
    std::cout << "array of 10 ints: " << sizeof(arr) << "\n";
    std::cout << "elements in arr: " << sizeof(arr) / sizeof(arr[0]) << "\n";
}

Expected output (on a typical 64-bit system):

int: 4
double: 8
bool: 1
array of 10 ints: 40
elements in arr: 10

Type Limits

The <limits> header provides information about type properties:

#include <iostream>
#include <limits>

int main() {
    std::cout << "int max: " << std::numeric_limits<int>::max() << "\n";
    std::cout << "int min: " << std::numeric_limits<int>::min() << "\n";
    std::cout << "double digits: " << std::numeric_limits<double>::digits10 << "\n";
    std::cout << "bool is signed: " << std::numeric_limits<bool>::is_signed << "\n";
}

Expected output:

int max: 2147483647
int min: -2147483648
double digits: 15
bool is signed: false

Type Conversion

Implicit Conversion

int i = 42;
double d = i;         // int to double, safe
double pi = 3.14;
int trunc = pi;       // double to int, truncates to 3 (warning)

Explicit Conversion (Casting)

double pi = 3.14159;
int approx = static_cast<int>(pi);        // C++ style
int old = (int)pi;                        // C style (avoid)

unsigned char byte = 200;
int promoted = byte;                       // implicit, safe

int big = 1000;
char small = static_cast<char>(big);       // narrowing, data may be lost

Prefer static_cast<> for well-defined conversions. It is searchable, visible, and checked by the compiler.

Common Mistakes

Mistake 1: Uninitialized Variables

int count;
std::cout << count;  // undefined behavior, may print garbage

Always initialize variables. Use {} for zero-initialization.

Mistake 2: Signed/Unsigned Mismatch

unsigned int u = 10;
int s = -5;
if (s < u) { ... }  // false! s converts to unsigned, becomes huge

Enable compiler warnings: -Wsign-compare (part of -Wall).

Mistake 3: Assuming Fixed Sizes

On a 32-bit system long is 4 bytes; on 64-bit Linux it is 8 bytes; on 64-bit Windows it is 4 bytes. Use sizeof() or fixed-width types from <cstdint>.

Mistake 4: Overflow

int x = 2147483647;
x = x + 1;  // undefined behavior for signed overflow

Use unsigned types for modular arithmetic, or detect overflow with <limits>.

Mistake 5: Narrowing Brace Initialization

int x{3.14};  // error: narrowing conversion
int y(3.14);  // OK (but truncates to 3)

Brace initialization protects against data loss.

Mistake 6: Using char for Arithmetic

char c = 200;  // implementation-defined if char is signed

Use unsigned char or uint8_t when storing byte values.

Practice Questions

  1. What is the difference between int x = 5;, int x(5);, and int x{5};?
  2. What does sizeof return for a bool? Why is that interesting?
  3. Write code that demonstrates an unsigned integer wrapping to zero.
  4. Use <cstdint> to declare a variable that is exactly 64 bits wide on all platforms.
  5. What is the output of std::cout << (10u < -5);? Why?

Challenge

Write a program that uses auto to deduce the type of a lambda expression (you can write a simple one: [](int x){ return x * 2; }). Use typeid to print the human-readable type name.

FAQ

Why does C++ have so many integer types?

Different types reflect hardware capabilities and memory constraints. Embedded systems may use 8-bit microcontrollers; HPC systems use 64-bit or 128-bit. Matching the type to the hardware gives optimal performance.

Is `int` guaranteed to be 32 bits?

No. The standard guarantees only that int is at least 16 bits and at least as large as short. Most modern platforms use 32-bit int, but always verify with sizeof or use fixed-width types.

When should I use `unsigned`?

Use unsigned for bit flags, array indices, sizes, and values that are inherently non-negative. Be careful when mixing with signed types in arithmetic or comparisons.

What is the difference between `char`, `signed char`, and `unsigned char`?

They are three distinct types. char's signedness is implementation-defined. signed char and unsigned char have explicit signedness. char is not the same as signed char even if they have the same range.

Why does `auto` strip reference-ness?

Auto follows template deduction rules. If you want a reference, write auto&. If you want to preserve const, write const auto&. Understanding this is essential when using auto with function return types.

What is the difference between `size_t` and `int`?

size_t is an unsigned integer type returned by sizeof and used for array indices. It is large enough to represent any object's size on the platform. Always use size_t for sizes and indices.

Mini Project

Write a program that prints a table showing the name, size in bytes, minimum value, and maximum value for each fundamental type: bool, char, short, int, long, long long, float, double. Use sizeof, std::numeric_limits, and format with std::cout.

#include <iostream>
#include <limits>
#include <type_traits>

int main() {
    std::cout << "Type        Size    Min                     Max\n";
    std::cout << "----        ----    ---                     ---\n";
    
    auto print = [](auto name, auto x) {
        using T = decltype(x);
        std::cout << name << "  " 
                  << sizeof(T) << "      "
                  << std::numeric_limits<T>::min() << "  "
                  << std::numeric_limits<T>::max() << "\n";
    };
    
    print("bool", bool{});
    print("char", char{});
    print("short", short{});
    print("int", int{});
    print("long", long{});
    print("long long", long long{});
    print("float", float{});
    print("double", double{});
}

What's Next

Types give your data meaning. The next lesson covers constants and modifiers: const, constexpr, consteval, volatile, and mutable. You will learn how to make values immutable and execute code at compile time.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro