Docs
README
Enums, Structs, and Unions in C++
Table of Contents
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