Docs

Enums Structs Unions

Enums, Structs, and Unions in C++

Table of Contents

  1. Enumerations (Enums)
  2. Structures (Structs)
  3. Unions
  4. Bit Fields
  5. Best Practices

Enumerations (Enums)

Enums create named integer constants for better code readability.

Traditional Enums (C-style)

// Basic enum declaration
enum Color {
    RED,        // 0
    GREEN,      // 1
    BLUE        // 2
};

// With explicit values
enum Status {
    PENDING = 1,
    APPROVED = 2,
    REJECTED = 3,
    CANCELLED = 100
};

// Usage
Color c = RED;
Status s = APPROVED;

⚠️ Problems with Traditional Enums

┌─────────────────────────────────────────────────────────────────────────────┐
│                    TRADITIONAL ENUM PROBLEMS                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  1. NO SCOPE - Values pollute enclosing namespace                           │
│     ┌──────────────────────────────────────────────────────────────────┐    │
│     │  enum Color { RED, GREEN, BLUE };                                │    │
│     │  enum TrafficLight { RED, YELLOW, GREEN }; // ❌ ERROR!         │    │
│     │                      ^^^          ^^^^^                          │    │
│     │                      Redefinition of RED and GREEN!              │    │
│     └──────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  2. IMPLICIT CONVERSION - Converts to int silently                          │
│     ┌──────────────────────────────────────────────────────────────────┐    │
│     │  Color c = RED;                                                  │    │
│     │  int x = c;        // Compiles! x = 0                           │    │
│     │  c = 42;           // May compile on some compilers!            │    │
│     │  if (c == 5) {...} // Compiles! Meaningless comparison          │    │
│     └──────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  3. NO TYPE SAFETY - Different enums can be compared                        │
│     ┌──────────────────────────────────────────────────────────────────┐    │
│     │  enum Fruit { APPLE, ORANGE };                                   │    │
│     │  enum Planet { MARS, VENUS };                                    │    │
│     │  if (APPLE == MARS) {...}  // Compiles! Both are 0              │    │
│     └──────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Enum Class (C++11) - The Solution ✅

// Scoped enum (strongly typed)
enum class Color {
    Red,
    Green,
    Blue
};

enum class TrafficLight {
    Red,        // No conflict with Color::Red
    Yellow,
    Green       // No conflict with Color::Green
};

// Must use scope operator
Color c = Color::Red;
TrafficLight t = TrafficLight::Red;

// No implicit conversion
// int x = c;           // ❌ ERROR!
int x = static_cast<int>(c);  // ✓ Explicit cast required

// Type-safe comparisons
// if (c == t) {...}    // ❌ ERROR! Different types
if (c == Color::Red) {...}  // ✓ OK

Enum Class Features

// Specify underlying type
enum class ErrorCode : uint32_t {
    None = 0,
    NotFound = 404,
    ServerError = 500,
    MaxValue = 0xFFFFFFFF
};

enum class SmallEnum : uint8_t {
    A, B, C, D  // Uses only 1 byte
};

// Forward declaration (requires underlying type)
enum class Status : int;

void processStatus(Status s);  // Can use before full definition

enum class Status : int {
    Active,
    Inactive,
    Pending
};

Enum Class Visualization

┌─────────────────────────────────────────────────────────────────────────────┐
│                      ENUM vs ENUM CLASS                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Traditional Enum:                  Enum Class:                              │
│  ┌─────────────────────┐           ┌─────────────────────┐                  │
│  │ enum Color {        │           │ enum class Color {  │                  │
│  │   RED,              │           │   Red,              │                  │
│  │   GREEN,            │           │   Green,            │                  │
│  │   BLUE              │           │   Blue              │                  │
│  │ };                  │           │ };                  │                  │
│  └─────────────────────┘           └─────────────────────┘                  │
│           │                                 │                                │
│           ▼                                 ▼                                │
│  ┌─────────────────────┐           ┌─────────────────────┐                  │
│  │  Global Namespace   │           │    Color Scope      │                  │
│  │  RED = 0            │           │  Color::Red = 0     │                  │
│  │  GREEN = 1          │           │  Color::Green = 1   │                  │
│  │  BLUE = 2           │           │  Color::Blue = 2    │                  │
│  └─────────────────────┘           └─────────────────────┘                  │
│                                                                              │
│  Access: RED                       Access: Color::Red                        │
│  Type:   int (implicit)            Type:   Color (strict)                   │
│  Safe:   No                        Safe:   Yes                              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Structures (Structs)

Structs group related data together. In C++, structs are almost identical to classes (default public access).

Basic Struct

// Define a struct
struct Point {
    double x;
    double y;
};

// Create and initialize
Point p1;           // Members uninitialized
Point p2 = {3.0, 4.0};  // Aggregate initialization
Point p3{1.0, 2.0};     // C++11 uniform initialization
Point p4 = {.x = 5.0, .y = 6.0};  // C++20 designated initializers

// Access members
p1.x = 10.0;
p1.y = 20.0;
double distance = sqrt(p1.x * p1.x + p1.y * p1.y);

Struct with Methods

struct Rectangle {
    double width;
    double height;

    // Member functions
    double area() const {
        return width * height;
    }

    double perimeter() const {
        return 2 * (width + height);
    }

    void scale(double factor) {
        width *= factor;
        height *= factor;
    }

    // Static member
    static Rectangle square(double side) {
        return {side, side};
    }
};

// Usage
Rectangle rect{10.0, 5.0};
cout << "Area: " << rect.area() << endl;
rect.scale(2.0);

Rectangle sq = Rectangle::square(5.0);

Struct vs Class

┌─────────────────────────────────────────────────────────────────────────────┐
│                         STRUCT vs CLASS                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌───────────────────────────┐    ┌───────────────────────────┐            │
│  │        struct             │    │         class             │            │
│  ├───────────────────────────┤    ├───────────────────────────┤            │
│  │ Default access: PUBLIC    │    │ Default access: PRIVATE   │            │
│  │ Default inheritance:      │    │ Default inheritance:      │            │
│  │   PUBLIC                  │    │   PRIVATE                 │            │
│  └───────────────────────────┘    └───────────────────────────┘            │
│                                                                              │
│  Convention:                                                                 │
│  • struct - Simple data containers (POD), no invariants                     │
│  • class  - Complex types with behavior, encapsulation, invariants          │
│                                                                              │
│  Examples:                                                                   │
│  struct Point { int x, y; };           // Simple data                       │
│  struct Color { uint8_t r, g, b, a; }; // Simple data                       │
│  class BankAccount { ... };            // Has invariants (balance >= 0)     │
│  class DatabaseConnection { ... };      // Complex behavior                 │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Nested Structs

struct Address {
    string street;
    string city;
    string country;
    int zipCode;
};

struct Person {
    string name;
    int age;
    Address address;  // Nested struct

    void print() const {
        cout << name << ", " << age << " years old\n";
        cout << "Address: " << address.street << ", "
             << address.city << ", " << address.country << endl;
    }
};

// Usage
Person p = {
    "John Doe",
    30,
    {"123 Main St", "New York", "USA", 10001}
};
p.print();

POD (Plain Old Data) Structs

// POD struct - can be used with C, memcpy, etc.
struct PodStruct {
    int id;
    double value;
    char name[50];
    // No virtual functions, no constructors, no private/protected
};

// Check if POD (C++11)
static_assert(std::is_pod<PodStruct>::value, "PodStruct must be POD");

// Modern check (C++17+)
static_assert(std::is_trivially_copyable_v<PodStruct>);
static_assert(std::is_standard_layout_v<PodStruct>);

Unions

Unions allow storing different types in the same memory location. Only one member can be active at a time.

Basic Union

union Value {
    int i;
    float f;
    char c;
};

// All members share the same memory
Value v;
cout << sizeof(v) << endl;  // Size of largest member (int or float = 4)

v.i = 42;        // Store int
cout << v.i;     // OK: 42

v.f = 3.14f;     // Now store float (overwrites int!)
cout << v.f;     // OK: 3.14
// cout << v.i;  // Undefined behavior! (reading inactive member)

Union Memory Layout

┌─────────────────────────────────────────────────────────────────────────────┐
│                        UNION MEMORY LAYOUT                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  union Value {                                                               │
│      int i;      // 4 bytes                                                  │
│      float f;    // 4 bytes                                                  │
│      char c;     // 1 byte                                                   │
│  };                                                                          │
│                                                                              │
│  Memory (4 bytes, overlapping):                                              │
│  ┌─────────┬─────────┬─────────┬─────────┐                                  │
│  │ Byte 0  │ Byte 1  │ Byte 2  │ Byte 3  │                                  │
│  ├─────────┴─────────┴─────────┴─────────┤                                  │
│  │              int i                     │  ← Uses all 4 bytes             │
│  ├───────────────────────────────────────┤                                  │
│  │             float f                    │  ← Uses all 4 bytes             │
│  ├─────────┬─────────────────────────────┤                                  │
│  │ char c  │       (unused)              │  ← Uses only 1 byte              │
│  └─────────┴─────────────────────────────┘                                  │
│                                                                              │
│  v.i = 42;     [00101010][00000000][00000000][00000000]                     │
│  v.f = 3.14f;  [11000011][11110101][01001000][01000000]                     │
│  v.c = 'A';    [01000001][????????][????????][????????]                     │
│                                                                              │
│  All members START at the SAME address!                                      │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Tagged Union Pattern

// Safe usage with a type tag
enum class ValueType { Int, Float, String };

struct TaggedValue {
    ValueType type;
    union {
        int i;
        float f;
        // string s;  // ❌ Can't have non-trivial types in union
    };

    void setInt(int val) {
        type = ValueType::Int;
        i = val;
    }

    void setFloat(float val) {
        type = ValueType::Float;
        f = val;
    }

    void print() const {
        switch (type) {
            case ValueType::Int:   cout << "Int: " << i << endl; break;
            case ValueType::Float: cout << "Float: " << f << endl; break;
            default: cout << "Unknown type" << endl;
        }
    }
};

std::variant (C++17) - Type-Safe Union

#include <variant>
#include <string>

// Type-safe alternative to union
std::variant<int, float, std::string> value;

value = 42;           // Store int
value = 3.14f;        // Store float
value = "hello";      // Store string

// Access with std::get
try {
    int i = std::get<int>(value);  // Throws if wrong type
} catch (std::bad_variant_access& e) {
    cout << "Wrong type!" << endl;
}

// Safe access with std::get_if
if (auto* p = std::get_if<std::string>(&value)) {
    cout << "String: " << *p << endl;
}

// Pattern matching with std::visit
std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>) {
        cout << "int: " << arg << endl;
    } else if constexpr (std::is_same_v<T, float>) {
        cout << "float: " << arg << endl;
    } else if constexpr (std::is_same_v<T, std::string>) {
        cout << "string: " << arg << endl;
    }
}, value);

Bit Fields

Pack multiple values into a single integer for memory efficiency.

Basic Bit Fields

struct StatusFlags {
    unsigned int isActive : 1;    // 1 bit
    unsigned int priority : 3;    // 3 bits (0-7)
    unsigned int errorCode : 4;   // 4 bits (0-15)
    unsigned int reserved : 24;   // 24 bits
};  // Total: 32 bits = 4 bytes

StatusFlags flags;
flags.isActive = 1;
flags.priority = 5;
flags.errorCode = 12;

// Single integer operations
struct Packed {
    uint8_t a : 4;  // 4 bits (0-15)
    uint8_t b : 4;  // 4 bits (0-15)
};  // Total: 1 byte

Bit Field Layout

┌─────────────────────────────────────────────────────────────────────────────┐
│                        BIT FIELD LAYOUT                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  struct StatusFlags {                                                        │
│      unsigned int isActive : 1;    // 1 bit                                 │
│      unsigned int priority : 3;    // 3 bits                                │
│      unsigned int errorCode : 4;   // 4 bits                                │
│      unsigned int reserved : 24;   // 24 bits                               │
│  };                                                                          │
│                                                                              │
│  32-bit integer layout:                                                      │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │  reserved (24 bits)              │err│ pri │a│                      │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│  │←──────── 24 bits ────────────────→│←4→│←3─→│1│                           │
│  bit 31                                                              bit 0   │
│                                                                              │
│  Example: isActive=1, priority=5, errorCode=12                              │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │  00000000 00000000 00000000       │1100│ 101 │1│                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                       │     │    └─ isActive = 1            │
│                                       │     └───── priority = 5 (101)       │
│                                       └───────── errorCode = 12 (1100)      │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Practical Bit Field Example

// Network packet header
struct IPv4Header {
    uint8_t version : 4;      // IP version
    uint8_t headerLength : 4; // Header length
    uint8_t tos;              // Type of service
    uint16_t totalLength;     // Total length
    uint16_t id;              // Identification
    uint16_t flags : 3;       // Flags
    uint16_t fragOffset : 13; // Fragment offset
    uint8_t ttl;              // Time to live
    uint8_t protocol;         // Protocol
    uint16_t checksum;        // Header checksum
    uint32_t srcAddr;         // Source address
    uint32_t destAddr;        // Destination address
};

// Color with alpha (RGBA)
struct Color32 {
    uint8_t r : 8;  // Red
    uint8_t g : 8;  // Green
    uint8_t b : 8;  // Blue
    uint8_t a : 8;  // Alpha
};  // Exactly 32 bits = 4 bytes

Best Practices

✅ Do

// 1. Use enum class instead of plain enum
enum class Direction { North, South, East, West };

// 2. Specify underlying type when needed
enum class ErrorCode : uint16_t { OK = 0, Error = 1 };

// 3. Use structs for simple data aggregates
struct Point { double x, y; };

// 4. Use std::variant instead of union (C++17+)
std::variant<int, double, string> value;

// 5. Initialize all struct members
Point p = {0.0, 0.0};  // or Point p{};

// 6. Use designated initializers (C++20)
Point p = {.x = 1.0, .y = 2.0};

❌ Don't

// 1. Don't use C-style enums
enum Color { RED, GREEN, BLUE };  // Use enum class

// 2. Don't compare different enum types
// if (Color::Red == Direction::North)  // Won't compile with enum class ✓

// 3. Don't rely on union type-punning (undefined behavior)
union { int i; float f; } u;
u.i = 42;
// float bad = u.f;  // UB! Use std::bit_cast or memcpy

// 4. Don't forget struct initialization
Point p;  // x and y are uninitialized garbage!

// 5. Don't use bit fields for portable binary formats
// (bit layout is implementation-defined)

Memory Comparison

┌─────────────────────────────────────────────────────────────────────────────┐
│                   STRUCT vs UNION vs BIT FIELD                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  STRUCT (sequential layout)              Size = sum of members + padding     │
│  ┌───────┬───────┬───────────────────┐                                      │
│  │ int a │ int b │     int c         │   = 12 bytes                         │
│  │(4 B)  │(4 B)  │     (4 B)         │                                      │
│  └───────┴───────┴───────────────────┘                                      │
│                                                                              │
│  UNION (overlapping)                     Size = largest member               │
│  ┌───────────────────────────────────┐                                      │
│  │ int a OR int b OR int c           │   = 4 bytes                          │
│  │         (4 B)                     │                                      │
│  └───────────────────────────────────┘                                      │
│                                                                              │
│  BIT FIELD (packed bits)                 Size = minimal for bits             │
│  ┌───────────────────────────────────┐                                      │
│  │ a:10 │ b:10 │ c:10 │ pad:2        │   = 4 bytes (32 bits)               │
│  └───────────────────────────────────┘                                      │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Quick Reference

// Enum class (preferred)
enum class Name : type { Value1, Value2 };
Name n = Name::Value1;
int i = static_cast<int>(n);

// Struct
struct Name {
    type member;
    type method() const { return member; }
};
Name obj = {value};
Name obj{value};

// Union (prefer std::variant)
union Name {
    type1 a;
    type2 b;
};

// std::variant (C++17)
std::variant<type1, type2> v;
std::get<type1>(v);
std::get_if<type1>(&v);
std::visit(visitor, v);

// Bit field
struct Name {
    type member : bits;
};

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples

# Check struct sizes
g++ -std=c++17 -Wall check_sizes.cpp -o check_sizes
./check_sizes
Enums Structs Unions - C++ Tutorial | DeepML