Working With Data

Enums, Structs, and Unions in C++


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

Recommended Conventions

✅ 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)               │
│  └───────────────────────────────────┘                                      │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Cheat Sheet

// 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