← Back to all projects

CPP MASTERY PROJECTS

Learning C++: From First Principles to Mastery

Core Concept Analysis

C++ is a multi-paradigm language offering unparalleled control over hardware while providing high-level abstractions. To truly understand C++, you need to grasp these fundamental building blocks:

1. Memory Model & Object Lifetime

  • Stack vs Heap: Automatic vs dynamic storage duration
  • RAII: Resource Acquisition Is Initialization—the cornerstone of C++ resource management
  • Ownership semantics: Who is responsible for freeing resources?
  • Value categories: lvalues, rvalues, and move semantics

2. The Type System

  • Static typing: Types checked at compile time
  • Type deduction: auto, decltype, template argument deduction
  • Const correctness: Immutability as a design tool
  • References vs pointers: When and why to use each

3. Object-Oriented Programming

  • Encapsulation: Data hiding and interface design
  • Inheritance: Virtual functions, polymorphism, abstract classes
  • Composition vs inheritance: Preferring composition
  • The Rule of Zero/Three/Five: Special member functions

4. Generic Programming

  • Templates: Function and class templates
  • Template metaprogramming: Compile-time computation
  • Concepts (C++20): Constraining templates
  • SFINAE: Substitution Failure Is Not An Error

5. The Standard Library (STL)

  • Containers: vector, map, set, unordered_map, etc.
  • Algorithms: sort, find, transform, accumulate, etc.
  • Iterators: The glue between containers and algorithms
  • Smart pointers: unique_ptr, shared_ptr, weak_ptr

6. Modern C++ (C++11 through C++23)

  • Move semantics: Efficient transfer of resources
  • Lambda expressions: Anonymous function objects
  • Range-based for loops: Cleaner iteration
  • Structured bindings: Destructuring like modern languages
  • Coroutines (C++20): Cooperative multitasking
  • Modules (C++20): Modern compilation model

7. Concurrency

  • Threads: std::thread, thread pools
  • Synchronization: mutex, lock_guard, condition_variable
  • Atomics: Lock-free programming
  • Futures and promises: Asynchronous results

8. Low-Level Control

  • Manual memory management: new/delete, placement new
  • Bit manipulation: Masks, shifts, unions
  • Inline assembly: When you need ultimate control
  • ABI considerations: Calling conventions, name mangling

Project-Based Learning Path

The following projects are ordered to progressively build your understanding, from basic syntax to systems-level mastery.


Project 1: Command-Line Calculator with Expression Parser

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: C, Rust, Go Coolness Level: Level 2: Practical but Forgettable Business Potential: 1. The “Resume Gold” Difficulty: Level 1: Beginner Knowledge Area: Parsing / Basic Syntax Software or Tool: Expression Parser Main Book: “C++ Primer Plus” by Stephen Prata

What you’ll build

A calculator that parses and evaluates mathematical expressions like 3 + 4 * (2 - 1) with proper operator precedence, supporting variables, and maintaining a history of calculations.

Why it teaches C++

This project forces you to understand fundamental C++ concepts: input/output streams, string manipulation, control flow, functions, and basic data structures. The expression parsing introduces you to recursion and the recursive descent parsing pattern—a preview of compiler concepts.

Core challenges you’ll face

  • Tokenizing input strings (splitting “3+42” into tokens) → maps to *string manipulation
  • Implementing operator precedence (multiplication before addition) → maps to recursive algorithms
  • Handling parentheses (nested expressions) → maps to stack-based evaluation or recursion
  • Managing variables (storing and retrieving named values) → maps to std::map or std::unordered_map
  • Error handling (malformed expressions) → maps to exceptions and error states

Key Concepts

  • String Streams: “C++ Primer Plus” Chapter 17 - Stephen Prata
  • Recursive Descent Parsing: “Compilers: Principles and Practice” Chapter 4 - Parag H. Dave
  • STL Maps: “The C++ Programming Language” Chapter 31 - Bjarne Stroustrup
  • Exception Handling: “C++ Primer Plus” Chapter 15 - Stephen Prata

Difficulty: Beginner Time estimate: 1-2 weeks Prerequisites: Basic programming concepts, understanding of arithmetic

Real world outcome

$ ./calculator
> 3 + 4 * 2
Result: 11

> x = 10
x = 10

> x * 2 + 5
Result: 25

> (1 + 2) * (3 + 4)
Result: 21

> 5 / 0
Error: Division by zero

> history
1: 3 + 4 * 2 = 11
2: x = 10
3: x * 2 + 5 = 25
4: (1 + 2) * (3 + 4) = 21

Implementation Hints

Use a two-phase approach:

  1. Tokenization: Convert string to vector of tokens (NUMBER, OPERATOR, PAREN, VARIABLE)
  2. Parsing: Use recursive descent with functions for each precedence level

Grammar (informal):

expression := term (('+' | '-') term)*
term       := factor (('*' | '/') factor)*
factor     := NUMBER | VARIABLE | '(' expression ')'

Pseudo code:

class Token:
    enum Type { NUMBER, PLUS, MINUS, MULT, DIV, LPAREN, RPAREN, VARIABLE, END }
    Type type
    double value  // for numbers
    string name   // for variables

class Tokenizer:
    string input
    int pos = 0

    Token nextToken():
        skipWhitespace()
        if pos >= input.length(): return Token(END)

        char c = input[pos]
        if isDigit(c):
            return parseNumber()
        if isAlpha(c):
            return parseVariable()
        if c == '+': pos++; return Token(PLUS)
        // ... other operators

class Parser:
    Tokenizer tokenizer
    Token currentToken
    map<string, double> variables

    double parseExpression():
        double left = parseTerm()
        while currentToken.type in [PLUS, MINUS]:
            op = currentToken.type
            advance()
            right = parseTerm()
            left = (op == PLUS) ? left + right : left - right
        return left

    double parseTerm():
        double left = parseFactor()
        while currentToken.type in [MULT, DIV]:
            op = currentToken.type
            advance()
            right = parseFactor()
            if op == DIV and right == 0:
                throw "Division by zero"
            left = (op == MULT) ? left * right : left / right
        return left

    double parseFactor():
        if currentToken.type == NUMBER:
            value = currentToken.value
            advance()
            return value
        if currentToken.type == VARIABLE:
            name = currentToken.name
            advance()
            return variables[name]
        if currentToken.type == LPAREN:
            advance()  // consume '('
            value = parseExpression()
            expect(RPAREN)
            return value
        throw "Unexpected token"

Learning milestones

  1. Basic arithmetic works → You understand input parsing and operators
  2. Precedence is correct → You understand recursive descent
  3. Variables store and retrieve → You understand std::map
  4. Parentheses work → You understand recursion
  5. Errors handled gracefully → You understand exception handling

Project 2: Memory-Mapped File Reader

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: C, Rust Coolness Level: Level 3: Genuinely Clever Business Potential: 2. The “Micro-SaaS / Pro Tool” Difficulty: Level 2: Intermediate Knowledge Area: Memory Management / File I/O Software or Tool: mmap / Memory Mapped Files Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build

A file reader that memory-maps files for efficient random access, supporting operations like word counting, searching, and displaying file sections—all without loading the entire file into memory.

Why it teaches C++

Memory mapping exposes you to how the OS manages virtual memory. You’ll work with raw pointers, understand address spaces, and see how C++ can interact with OS-level APIs. This is foundational for high-performance file processing.

Core challenges you’ll face

  • Understanding virtual memory (pages, mapping, protection) → maps to OS memory concepts
  • Using mmap/MapViewOfFile (platform-specific APIs) → maps to system programming
  • Safe pointer handling (avoiding access violations) → maps to pointer arithmetic
  • Cross-platform abstraction (POSIX vs Windows) → maps to conditional compilation
  • Handling large files (larger than RAM) → maps to understanding memory mapping benefits

Key Concepts

  • Memory Mapping: “The Linux Programming Interface” Chapter 49 - Michael Kerrisk
  • Virtual Memory: “Computer Systems: A Programmer’s Perspective” Chapter 9 - Bryant & O’Hallaron
  • File I/O: “C++ Primer Plus” Chapter 17 - Stephen Prata
  • Platform Abstraction: “C++ Primer Plus” Chapter 9 - Stephen Prata

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1, understanding of pointers, basic file I/O

Real world outcome

$ ./mmap_reader largefile.log

File: largefile.log (4.2 GB)
Mapped successfully.

Commands:
  count - Count lines/words
  search <pattern> - Find pattern
  view <start> <end> - Show byte range
  quit - Exit

> count
Lines: 45,234,891
Words: 312,456,789
Chars: 4,512,345,678
(Completed in 1.2 seconds - file never fully loaded to RAM)

> search "ERROR"
Found 1,234 occurrences
First 5:
  Line 45: [2024-01-15 10:23:45] ERROR: Connection timeout
  Line 892: [2024-01-15 10:25:12] ERROR: Database unreachable
  ...

> view 0 1000
[Shows first 1000 bytes of file]

Implementation Hints

Memory mapping makes a file appear as a contiguous block of memory. The OS handles paging—only accessed pages are loaded from disk.

On POSIX (Linux/macOS):

int fd = open("file.txt", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
void* mapped = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
// Now 'mapped' points to file contents
// Access mapped[0] to mapped[sb.st_size-1]
munmap(mapped, sb.st_size);
close(fd);

On Windows:

HANDLE hFile = CreateFile(...);
HANDLE hMapping = CreateFileMapping(hFile, ...);
void* mapped = MapViewOfFile(hMapping, ...);

Pseudo code for cross-platform wrapper:

class MemoryMappedFile:
    void* data = nullptr
    size_t size = 0
    #ifdef _WIN32
        HANDLE hFile, hMapping
    #else
        int fd
    #endif

    bool open(const string& path):
        #ifdef _WIN32
            hFile = CreateFile(path.c_str(), GENERIC_READ, ...)
            if hFile == INVALID_HANDLE_VALUE: return false
            size = GetFileSize(hFile, nullptr)
            hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, ...)
            data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)
        #else
            fd = ::open(path.c_str(), O_RDONLY)
            if fd < 0: return false
            struct stat sb
            fstat(fd, &sb)
            size = sb.st_size
            data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0)
            if data == MAP_FAILED: return false
        #endif
        return true

    const char* begin() const: return static_cast<const char*>(data)
    const char* end() const: return begin() + size

    size_t countLines():
        count = 0
        for ptr = begin(); ptr < end(); ptr++:
            if *ptr == '\n': count++
        return count

    vector<size_t> search(const string& pattern):
        results = []
        // Use std::search or Boyer-Moore for efficiency
        for match in findAll(begin(), end(), pattern):
            results.push_back(match - begin())
        return results

    ~MemoryMappedFile():
        #ifdef _WIN32
            UnmapViewOfFile(data)
            CloseHandle(hMapping)
            CloseHandle(hFile)
        #else
            munmap(data, size)
            close(fd)
        #endif

Learning milestones

  1. File maps successfully → You understand mmap/MapViewOfFile
  2. Pointer access works → You understand pointer arithmetic
  3. Large files don’t crash → You understand virtual memory
  4. Cross-platform works → You understand conditional compilation
  5. Resources cleaned up → You understand RAII pattern

Project 3: Smart Pointer Implementation

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: Rust (for comparison) Coolness Level: Level 4: Hardcore Tech Flex Business Potential: 1. The “Resume Gold” Difficulty: Level 3: Advanced Knowledge Area: Memory Management / RAII Software or Tool: Custom Smart Pointers Main Book: “Effective Modern C++” by Scott Meyers

What you’ll build

Your own implementation of unique_ptr, shared_ptr, and weak_ptr with proper move semantics, reference counting, custom deleters, and weak reference support.

Why it teaches C++

This is the project for understanding C++ memory management. You’ll implement RAII, understand move semantics deeply, deal with thread-safe reference counting, and handle the subtle weak_ptr/shared_ptr interaction. This is what separates C++ developers from C++ masters.

Core challenges you’ll face

  • Implementing unique_ptr (exclusive ownership, move-only) → maps to move semantics
  • Implementing shared_ptr (reference counting, thread safety) → maps to atomic operations
  • Implementing weak_ptr (non-owning, lock to shared) → maps to control block design
  • Custom deleters (type-erased callables) → maps to type erasure
  • make_shared optimization (single allocation for object + control block) → maps to memory layout

Key Concepts

  • Move Semantics: “Effective Modern C++” Items 23-30 - Scott Meyers
  • Smart Pointers: “Effective Modern C++” Items 18-22 - Scott Meyers
  • Atomic Operations: “C++ Concurrency in Action” Chapter 5 - Anthony Williams
  • RAII: Stanford CS106L Lecture on RAII

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-2, understanding of constructors/destructors, move semantics basics

Real world outcome

// Your implementation works like the standard library:

// unique_ptr - exclusive ownership
MyUniquePtr<Widget> up1 = makeUnique<Widget>(42);
// MyUniquePtr<Widget> up2 = up1;  // ERROR: deleted copy constructor
MyUniquePtr<Widget> up2 = std::move(up1);  // OK: transfer ownership
assert(up1.get() == nullptr);
assert(up2->getValue() == 42);

// shared_ptr - shared ownership
MySharedPtr<Widget> sp1 = makeShared<Widget>(100);
MySharedPtr<Widget> sp2 = sp1;  // OK: shared ownership
assert(sp1.use_count() == 2);
sp1.reset();
assert(sp2.use_count() == 1);
assert(sp2->getValue() == 100);

// weak_ptr - non-owning observer
MyWeakPtr<Widget> wp = sp2;
assert(!wp.expired());
if (auto locked = wp.lock()) {
    // Use locked safely
}
sp2.reset();
assert(wp.expired());

// Custom deleter
MyUniquePtr<FILE, decltype(&fclose)> file(fopen("test.txt", "r"), &fclose);

Implementation Hints

Control Block Architecture (for shared_ptr/weak_ptr):

+------------------+
| Control Block    |
|------------------|
| strong_count: 2  |  <- shared_ptr count
| weak_count: 1    |  <- weak_ptr count + 1 (if strong > 0)
| T* ptr           |  <- pointer to managed object
| Deleter          |  <- how to delete the object
+------------------+

The control block is destroyed when both strong_count and weak_count reach 0.

Pseudo code for unique_ptr:

template<typename T, typename Deleter = default_delete<T>>
class MyUniquePtr:
    T* ptr = nullptr
    Deleter deleter

    // Disable copy
    MyUniquePtr(const MyUniquePtr&) = delete
    MyUniquePtr& operator=(const MyUniquePtr&) = delete

    // Enable move
    MyUniquePtr(MyUniquePtr&& other) noexcept:
        ptr = other.ptr
        deleter = move(other.deleter)
        other.ptr = nullptr

    MyUniquePtr& operator=(MyUniquePtr&& other) noexcept:
        if this != &other:
            reset()
            ptr = other.ptr
            deleter = move(other.deleter)
            other.ptr = nullptr
        return *this

    ~MyUniquePtr():
        reset()

    void reset(T* p = nullptr):
        if ptr:
            deleter(ptr)
        ptr = p

    T* release():
        T* p = ptr
        ptr = nullptr
        return p

    T* get() const: return ptr
    T& operator*() const: return *ptr
    T* operator->() const: return ptr
    explicit operator bool() const: return ptr != nullptr

Pseudo code for shared_ptr control block:

struct ControlBlockBase:
    atomic<size_t> strong_count = 1
    atomic<size_t> weak_count = 1  // Extra 1 for strong refs

    virtual void destroy_object() = 0
    virtual void destroy_self() = 0

    void add_strong(): strong_count.fetch_add(1, memory_order_relaxed)

    void release_strong():
        if strong_count.fetch_sub(1, memory_order_acq_rel) == 1:
            destroy_object()
            release_weak()  // Release the extra weak ref

    void add_weak(): weak_count.fetch_add(1, memory_order_relaxed)

    void release_weak():
        if weak_count.fetch_sub(1, memory_order_acq_rel) == 1:
            destroy_self()

    bool try_lock():  // For weak_ptr::lock()
        size_t count = strong_count.load(memory_order_relaxed)
        while count > 0:
            if strong_count.compare_exchange_weak(count, count + 1):
                return true
        return false

template<typename T, typename Deleter>
struct ControlBlock : ControlBlockBase:
    T* ptr
    Deleter deleter

    void destroy_object() override:
        deleter(ptr)

    void destroy_self() override:
        delete this

Learning milestones

  1. unique_ptr works → You understand exclusive ownership and move semantics
  2. shared_ptr reference counting works → You understand RAII with shared ownership
  3. Thread-safe with atomics → You understand memory ordering
  4. weak_ptr locks correctly → You understand the control block pattern
  5. Custom deleters work → You understand type erasure basics

Project 4: Generic Container Library

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: Rust, D Coolness Level: Level 3: Genuinely Clever Business Potential: 1. The “Resume Gold” Difficulty: Level 3: Advanced Knowledge Area: Templates / Generic Programming Software or Tool: STL-like Containers Main Book: “The C++ Programming Language” by Bjarne Stroustrup

What you’ll build

A mini-STL with your own implementations of vector, list, unordered_map, and key algorithms (sort, find, transform), complete with iterators that work with standard algorithms.

Why it teaches C++

Implementing STL containers teaches you templates, memory allocation strategies, iterator design, and algorithm complexity. You’ll understand why the STL is designed the way it is and learn to write truly generic code.

Core challenges you’ll face

  • Template class design (type parameters, specialization) → maps to template fundamentals
  • Iterator implementation (categories, traits, compatibility) → maps to iterator concepts
  • Memory allocation strategies (growth factor, reallocation) → maps to allocator design
  • Exception safety (strong guarantee for push_back) → maps to exception safety guarantees
  • Move semantics for elements (efficient reallocation) → maps to move operations

Key Concepts

  • Templates: “The C++ Programming Language” Chapters 23-25 - Bjarne Stroustrup
  • STL Design: “Effective STL” - Scott Meyers
  • Iterator Concepts: cppreference - Iterator library
  • Allocators: “The C++ Programming Language” Chapter 34 - Bjarne Stroustrup

Difficulty: Advanced Time estimate: 3-4 weeks Prerequisites: Projects 1-3, template basics, understanding of complexity

Real world outcome

// Your vector works like std::vector
MyVector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";  // 1 2 3
}

// Works with standard algorithms!
std::sort(v.begin(), v.end(), std::greater<int>());
// v is now: 3 2 1

// Your list is a doubly-linked list
MyList<std::string> names;
names.push_back("Alice");
names.push_front("Bob");
auto it = std::find(names.begin(), names.end(), "Alice");
names.insert(it, "Charlie");  // Insert before Alice

// Your hash map
MyUnorderedMap<std::string, int> ages;
ages["Alice"] = 30;
ages["Bob"] = 25;
std::cout << ages.at("Alice");  // 30

Implementation Hints

Vector growth strategy: Typically double capacity when full. This gives amortized O(1) push_back.

Iterator requirements: For random-access iterator (like vector), you need:

  • operator*, operator-> (dereference)
  • operator++, operator-- (increment/decrement)
  • operator+, operator-, operator[] (random access)
  • operator==, operator!=, operator<, etc. (comparison)

Pseudo code for MyVector:

template<typename T, typename Allocator = std::allocator<T>>
class MyVector:
    T* data_ = nullptr
    size_t size_ = 0
    size_t capacity_ = 0
    Allocator alloc_

    // Iterator is just a pointer for vector
    using iterator = T*
    using const_iterator = const T*

    iterator begin(): return data_
    iterator end(): return data_ + size_
    const_iterator begin() const: return data_
    const_iterator end() const: return data_ + size_

    void push_back(const T& value):
        if size_ == capacity_:
            reserve(capacity_ == 0 ? 1 : capacity_ * 2)
        // Use placement new and allocator
        std::allocator_traits<Allocator>::construct(alloc_, data_ + size_, value)
        size_++

    void push_back(T&& value):  // Move version
        if size_ == capacity_:
            reserve(capacity_ == 0 ? 1 : capacity_ * 2)
        std::allocator_traits<Allocator>::construct(alloc_, data_ + size_, std::move(value))
        size_++

    void reserve(size_t new_cap):
        if new_cap <= capacity_: return

        T* new_data = std::allocator_traits<Allocator>::allocate(alloc_, new_cap)

        // Move existing elements (exception-safe version)
        size_t i = 0
        try:
            for (; i < size_; i++):
                std::allocator_traits<Allocator>::construct(
                    alloc_, new_data + i, std::move_if_noexcept(data_[i]))
        catch (...):
            // Destroy what we constructed and deallocate
            for (size_t j = 0; j < i; j++):
                std::allocator_traits<Allocator>::destroy(alloc_, new_data + j)
            std::allocator_traits<Allocator>::deallocate(alloc_, new_data, new_cap)
            throw

        // Destroy old elements and deallocate
        for (size_t i = 0; i < size_; i++):
            std::allocator_traits<Allocator>::destroy(alloc_, data_ + i)
        std::allocator_traits<Allocator>::deallocate(alloc_, data_, capacity_)

        data_ = new_data
        capacity_ = new_cap

    T& operator[](size_t index): return data_[index]
    const T& operator[](size_t index) const: return data_[index]

    T& at(size_t index):
        if index >= size_: throw std::out_of_range("Index out of bounds")
        return data_[index]

    ~MyVector():
        for (size_t i = 0; i < size_; i++):
            std::allocator_traits<Allocator>::destroy(alloc_, data_ + i)
        std::allocator_traits<Allocator>::deallocate(alloc_, data_, capacity_)

Learning milestones

  1. Vector grows correctly → You understand reallocation
  2. Iterators work with std::sort → You understand iterator requirements
  3. Exception safety maintained → You understand the strong guarantee
  4. Move semantics used → You understand efficient element transfer
  5. unordered_map has O(1) average lookup → You understand hash tables

Project 5: Thread Pool Library

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: Rust, Go, Java Coolness Level: Level 4: Hardcore Tech Flex Business Potential: 3. The “Service & Support” Model Difficulty: Level 3: Advanced Knowledge Area: Concurrency / Multithreading Software or Tool: Thread Pool Main Book: “C++ Concurrency in Action” by Anthony Williams

What you’ll build

A thread pool that manages a fixed number of worker threads, accepts tasks via a queue, returns futures for results, and supports graceful shutdown with proper synchronization.

Why it teaches C++

Concurrency is where C++ shines and where bugs hide. You’ll learn mutexes, condition variables, futures, and the subtle art of avoiding deadlocks and race conditions. This is essential for any high-performance C++ application.

Core challenges you’ll face

  • Managing worker threads (creation, joining, signaling) → maps to std::thread
  • Thread-safe task queue (producer-consumer pattern) → maps to mutex + condition_variable
  • Returning results (futures and promises) → maps to std::future/std::promise
  • Graceful shutdown (stopping without losing tasks) → maps to synchronization patterns
  • Avoiding deadlocks (lock ordering, avoiding nested locks) → maps to deadlock prevention

Key Concepts

  • Threads and Mutexes: “C++ Concurrency in Action” Chapters 2-3 - Anthony Williams
  • Condition Variables: “C++ Concurrency in Action” Chapter 4 - Anthony Williams
  • Futures and Promises: std::async and std::future - C++ Stories
  • Thread Pools: “C++ Concurrency in Action” Chapter 9 - Anthony Williams

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-4, basic threading concepts

Real world outcome

// Create pool with 4 worker threads
ThreadPool pool(4);

// Submit tasks and get futures
auto future1 = pool.submit([]() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
});

auto future2 = pool.submit([](int a, int b) {
    return a + b;
}, 10, 20);

// Do other work while tasks execute...

// Get results (blocks until ready)
std::cout << future1.get();  // 42
std::cout << future2.get();  // 30

// Submit many tasks
std::vector<std::future<int>> results;
for (int i = 0; i < 1000; i++) {
    results.push_back(pool.submit([i]() {
        return i * i;
    }));
}

// Collect all results
for (auto& f : results) {
    std::cout << f.get() << " ";
}

// Pool destructor waits for all tasks to complete

Implementation Hints

The core pattern:

  1. Create N worker threads that loop forever
  2. Workers wait on a condition variable for tasks
  3. Main thread pushes tasks to queue and notifies workers
  4. Tasks are wrapped to store result in a promise

Pseudo code:

class ThreadPool:
    vector<thread> workers
    queue<function<void()>> tasks
    mutex queue_mutex
    condition_variable condition
    bool stop = false

    ThreadPool(size_t num_threads):
        for i in 0 to num_threads:
            workers.emplace_back([this]() { worker_loop(); })

    void worker_loop():
        while true:
            function<void()> task

            {
                unique_lock<mutex> lock(queue_mutex)

                // Wait until there's a task or we're stopping
                condition.wait(lock, [this]() {
                    return stop || !tasks.empty()
                })

                // If stopping and no tasks, exit
                if stop && tasks.empty():
                    return

                // Get next task
                task = move(tasks.front())
                tasks.pop()
            }

            // Execute task outside the lock
            task()

    template<typename F, typename... Args>
    auto submit(F&& f, Args&&... args) -> future<decltype(f(args...))>:
        using return_type = decltype(f(args...))

        // Create packaged_task (wraps function with promise)
        auto task = make_shared<packaged_task<return_type()>>(
            bind(forward<F>(f), forward<Args>(args)...)
        )

        future<return_type> result = task->get_future()

        {
            unique_lock<mutex> lock(queue_mutex)
            if stop:
                throw runtime_error("Cannot submit to stopped pool")

            tasks.emplace([task]() { (*task)(); })
        }

        condition.notify_one()
        return result

    ~ThreadPool():
        {
            unique_lock<mutex> lock(queue_mutex)
            stop = true
        }
        condition.notify_all()  // Wake all workers

        for thread& worker : workers:
            if worker.joinable():
                worker.join()

Learning milestones

  1. Workers process tasks → You understand thread creation and queues
  2. Results returned via futures → You understand promise/future pattern
  3. No race conditions → You understand mutex and condition_variable
  4. Graceful shutdown → You understand proper synchronization
  5. Handles many tasks efficiently → You understand producer-consumer pattern

Project 6: JSON Parser Library

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: C, Rust, Go Coolness Level: Level 3: Genuinely Clever Business Potential: 3. The “Service & Support” Model Difficulty: Level 2: Intermediate Knowledge Area: Parsing / Data Structures Software or Tool: JSON Parser Main Book: “Language Implementation Patterns” by Terence Parr

What you’ll build

A complete JSON parser that reads JSON text into a DOM (Document Object Model), supports all JSON types, allows querying/modification, and serializes back to JSON with pretty-printing.

Why it teaches C++

JSON parsing combines string processing, recursive data structures, and polymorphism (or std::variant). You’ll learn to design APIs, handle edge cases, and create a library that others would want to use.

Core challenges you’ll face

  • Tokenizing JSON (strings, numbers, literals, punctuation) → maps to lexical analysis
  • Recursive parsing (objects containing arrays containing objects) → maps to recursive descent
  • Representing JSON values (variant types or inheritance) → maps to std::variant or polymorphism
  • Unicode handling (\uXXXX escape sequences) → maps to character encoding
  • Error reporting (line/column numbers, clear messages) → maps to parser diagnostics

Key Concepts

  • Recursive Descent Parsing: “Language Implementation Patterns” Chapter 2 - Terence Parr
  • std::variant: “C++17 - The Complete Guide” Chapter 16 - Nicolai Josuttis
  • String Processing: “The C++ Programming Language” Chapter 36 - Bjarne Stroustrup
  • Unicode: UTF-8 and Unicode FAQ

Difficulty: Intermediate Time estimate: 2-3 weeks Prerequisites: Projects 1-2, string manipulation, recursive thinking

Real world outcome

// Parse JSON string
std::string json_text = R"({
    "name": "Alice",
    "age": 30,
    "hobbies": ["reading", "coding"],
    "address": {
        "city": "Seattle",
        "zip": "98101"
    }
})";

Json json = Json::parse(json_text);

// Access values
std::cout << json["name"].as_string();  // "Alice"
std::cout << json["age"].as_int();      // 30
std::cout << json["hobbies"][0].as_string();  // "reading"
std::cout << json["address"]["city"].as_string();  // "Seattle"

// Modify
json["age"] = 31;
json["hobbies"].push_back("hiking");

// Serialize back
std::cout << json.dump(2);  // Pretty-print with 2-space indent

// Handle errors
try {
    Json bad = Json::parse("{ invalid json }");
} catch (const JsonParseError& e) {
    std::cerr << e.what();  // "Parse error at line 1, column 3: Expected string key"
}

Implementation Hints

JSON types: null, boolean, number, string, array, object

Use std::variant for type-safe union:

using JsonValue = std::variant<
    std::nullptr_t,           // null
    bool,                     // boolean
    double,                   // number
    std::string,              // string
    std::vector<Json>,        // array
    std::map<std::string, Json>  // object
>;

Pseudo code:

class Json:
    variant<nullptr_t, bool, double, string, vector<Json>, map<string, Json>> value_

    static Json parse(string_view input):
        Tokenizer tokenizer(input)
        return parseValue(tokenizer)

    static Json parseValue(Tokenizer& tok):
        Token t = tok.peek()
        switch t.type:
            case NULL_LITERAL: tok.advance(); return Json(nullptr)
            case TRUE_LITERAL: tok.advance(); return Json(true)
            case FALSE_LITERAL: tok.advance(); return Json(false)
            case NUMBER: tok.advance(); return Json(parseDouble(t.text))
            case STRING: tok.advance(); return Json(parseString(t.text))
            case LBRACKET: return parseArray(tok)
            case LBRACE: return parseObject(tok)
            default: throw JsonParseError("Unexpected token", tok.line(), tok.column())

    static Json parseArray(Tokenizer& tok):
        tok.expect(LBRACKET)
        vector<Json> arr
        if tok.peek().type != RBRACKET:
            arr.push_back(parseValue(tok))
            while tok.peek().type == COMMA:
                tok.advance()
                arr.push_back(parseValue(tok))
        tok.expect(RBRACKET)
        return Json(move(arr))

    static Json parseObject(Tokenizer& tok):
        tok.expect(LBRACE)
        map<string, Json> obj
        if tok.peek().type != RBRACE:
            string key = tok.expectString()
            tok.expect(COLON)
            obj[key] = parseValue(tok)
            while tok.peek().type == COMMA:
                tok.advance()
                key = tok.expectString()
                tok.expect(COLON)
                obj[key] = parseValue(tok)
        tok.expect(RBRACE)
        return Json(move(obj))

    Json& operator[](const string& key):
        return get<map<string, Json>>(value_)[key]

    Json& operator[](size_t index):
        return get<vector<Json>>(value_)[index]

    string dump(int indent = 0) const:
        // Recursive serialization with formatting

Learning milestones

  1. Parses basic JSON → You understand tokenization and parsing
  2. Handles nested structures → You understand recursion
  3. Type access works → You understand std::variant
  4. Unicode escapes work → You understand character encoding
  5. Pretty-printing works → You understand serialization

Project 7: TCP Chat Server

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: C, Rust, Go Coolness Level: Level 3: Genuinely Clever Business Potential: 2. The “Micro-SaaS / Pro Tool” Difficulty: Level 3: Advanced Knowledge Area: Network Programming / Sockets Software or Tool: TCP Sockets Main Book: “TCP/IP Sockets in C” by Michael J. Donahoo

What you’ll build

A multi-client chat server using TCP sockets, supporting rooms, private messages, nicknames, and a simple protocol—all with proper connection handling and graceful shutdown.

Why it teaches C++

Network programming exposes you to blocking vs non-blocking I/O, the select/poll/epoll patterns, and protocol design. You’ll handle multiple clients concurrently and deal with the messiness of real network communication.

Core challenges you’ll face

  • Socket creation and binding (socket, bind, listen, accept) → maps to socket API
  • Handling multiple clients (select, poll, or threads) → maps to I/O multiplexing
  • Protocol design (message framing, commands) → maps to application protocol
  • Graceful disconnection (detecting closed connections) → maps to error handling
  • Cross-platform sockets (POSIX vs Winsock) → maps to platform abstraction

Key Concepts

  • Socket Programming: “TCP/IP Sockets in C” Chapters 1-4 - Donahoo & Calvert
  • I/O Multiplexing: “The Linux Programming Interface” Chapter 63 - Michael Kerrisk
  • Network Byte Order: GeeksforGeeks - Socket Programming
  • Protocol Design: “TCP/IP Illustrated, Volume 1” - W. Richard Stevens

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-5, basic networking concepts

Real world outcome

# Terminal 1 - Start server
$ ./chat_server 8080
Server listening on port 8080...
Client connected from 127.0.0.1:54321
Client connected from 127.0.0.1:54322

# Terminal 2 - Client 1
$ nc localhost 8080
/nick Alice
Your nickname is now Alice
/join general
Joined room: general
Hello everyone!
[Bob]: Hi Alice!
/msg Bob Hey, want to chat privately?
[Private from Bob]: Sure!
/rooms
Available rooms: general, random
/quit
Goodbye!

# Terminal 3 - Client 2
$ nc localhost 8080
/nick Bob
Your nickname is now Bob
/join general
Joined room: general
[Alice]: Hello everyone!
Hi Alice!
[Private from Alice]: Hey, want to chat privately?
/msg Alice Sure!

Implementation Hints

Use select() or poll() for handling multiple clients in a single thread, or use one thread per client.

Message framing: Since TCP is a stream protocol, you need a way to know where messages end. Options:

  1. Newline-delimited (simple, used here)
  2. Length-prefixed (more robust for binary)

Pseudo code:

class ChatServer:
    int server_socket
    map<int, Client> clients  // socket fd -> client info
    map<string, set<int>> rooms  // room name -> set of client fds

    struct Client:
        string nickname = "Anonymous"
        string current_room = ""
        string read_buffer = ""  // Partial message buffer

    void run(int port):
        server_socket = socket(AF_INET, SOCK_STREAM, 0)

        sockaddr_in addr
        addr.sin_family = AF_INET
        addr.sin_addr.s_addr = INADDR_ANY
        addr.sin_port = htons(port)

        bind(server_socket, (sockaddr*)&addr, sizeof(addr))
        listen(server_socket, SOMAXCONN)

        while running:
            fd_set read_fds
            FD_ZERO(&read_fds)
            FD_SET(server_socket, &read_fds)
            int max_fd = server_socket

            for (fd, client) in clients:
                FD_SET(fd, &read_fds)
                max_fd = max(max_fd, fd)

            select(max_fd + 1, &read_fds, nullptr, nullptr, nullptr)

            // New connection?
            if FD_ISSET(server_socket, &read_fds):
                int client_fd = accept(server_socket, ...)
                clients[client_fd] = Client()
                send(client_fd, "Welcome! Use /nick <name> to set nickname\n")

            // Data from existing clients?
            for (fd, client) in clients:
                if FD_ISSET(fd, &read_fds):
                    char buffer[1024]
                    int n = recv(fd, buffer, sizeof(buffer), 0)
                    if n <= 0:
                        handleDisconnect(fd)
                    else:
                        client.read_buffer.append(buffer, n)
                        processMessages(fd)

    void processMessages(int fd):
        Client& client = clients[fd]
        while (pos = client.read_buffer.find('\n')) != string::npos:
            string message = client.read_buffer.substr(0, pos)
            client.read_buffer.erase(0, pos + 1)
            handleMessage(fd, message)

    void handleMessage(int fd, string message):
        Client& client = clients[fd]

        if message.starts_with("/nick "):
            client.nickname = message.substr(6)
            send(fd, "Your nickname is now " + client.nickname + "\n")

        else if message.starts_with("/join "):
            string room = message.substr(6)
            if !client.current_room.empty():
                rooms[client.current_room].erase(fd)
            client.current_room = room
            rooms[room].insert(fd)
            send(fd, "Joined room: " + room + "\n")

        else if message.starts_with("/msg "):
            // Parse "/msg <target> <message>"
            // Find target client and send private message

        else if !client.current_room.empty():
            // Broadcast to room
            string broadcast = "[" + client.nickname + "]: " + message + "\n"
            for other_fd in rooms[client.current_room]:
                if other_fd != fd:
                    send(other_fd, broadcast)

Learning milestones

  1. Server accepts connections → You understand socket(), bind(), listen(), accept()
  2. Multiple clients work → You understand select() or poll()
  3. Messages delivered to room → You understand broadcast pattern
  4. Private messages work → You understand routing
  5. Graceful disconnect → You understand connection cleanup

Project 8: Simple Database Engine

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: C, Rust Coolness Level: Level 4: Hardcore Tech Flex Business Potential: 4. The “Open Core” Infrastructure Difficulty: Level 4: Expert Knowledge Area: Storage / B-Trees / SQL Software or Tool: Database Engine Main Book: “Database Internals” by Alex Petrov

What you’ll build

A simple relational database engine with a B-tree index, page-based storage, a basic SQL parser (SELECT, INSERT, CREATE TABLE), and ACID transactions with write-ahead logging.

Why it teaches C++

Databases are the ultimate systems programming challenge. You’ll manage raw bytes on disk, implement sophisticated data structures, handle concurrent access, and ensure durability. This project touches nearly every aspect of C++ systems programming.

Core challenges you’ll face

  • Page-based storage (fixed-size pages, buffer pool) → maps to disk I/O patterns
  • B-tree implementation (insertion, splitting, searching) → maps to tree data structures
  • SQL parsing (lexing, parsing, query planning) → maps to language processing
  • Transaction management (BEGIN, COMMIT, ROLLBACK) → maps to ACID properties
  • Write-ahead logging (durability, crash recovery) → maps to journaling

Key Concepts

  • B-Trees: “Introduction to Algorithms” Chapter 18 - Cormen et al.
  • Database Storage: “Database Internals” Chapters 1-4 - Alex Petrov
  • Write-Ahead Logging: “Database Internals” Chapter 10 - Alex Petrov
  • SQL Parsing: “Language Implementation Patterns” Chapter 5 - Terence Parr

Difficulty: Expert Time estimate: 1-2 months Prerequisites: Projects 1-7, B-tree understanding, file I/O

Real world outcome

$ ./mydb data.db

mydb> CREATE TABLE users (id INT PRIMARY KEY, name TEXT, email TEXT);
Table 'users' created.

mydb> INSERT INTO users VALUES (1, 'Alice', 'alice@example.com');
1 row inserted.

mydb> INSERT INTO users VALUES (2, 'Bob', 'bob@example.com');
1 row inserted.

mydb> SELECT * FROM users;
+----+-------+-------------------+
| id | name  | email             |
+----+-------+-------------------+
|  1 | Alice | alice@example.com |
|  2 | Bob   | bob@example.com   |
+----+-------+-------------------+
2 rows returned.

mydb> SELECT name FROM users WHERE id = 1;
+-------+
| name  |
+-------+
| Alice |
+-------+
1 row returned.

mydb> BEGIN;
Transaction started.

mydb> UPDATE users SET name = 'Alice Smith' WHERE id = 1;
1 row updated.

mydb> ROLLBACK;
Transaction rolled back.

mydb> SELECT name FROM users WHERE id = 1;
+-------+
| name  |
+-------+
| Alice |  -- Unchanged due to rollback
+-------+

Implementation Hints

Page structure (4KB pages):

+------------------+
| Page Header      |  (page type, num slots, free space pointer)
+------------------+
| Slot Directory   |  (array of (offset, length) for each record)
+------------------+
| Free Space       |
+------------------+
| Record Data      |  (grows from bottom up)
+------------------+

B-tree for index:

  • Internal nodes: keys + child page pointers
  • Leaf nodes: keys + row IDs (or full rows for clustered index)

WAL (Write-Ahead Log):

  • Before modifying any page, write the change to the log
  • On commit, flush log to disk
  • On crash recovery, replay log

Pseudo code for B-tree insert:

class BTree:
    Page* root

    void insert(Key key, Value value):
        if root.isFull():
            // Split root, create new root
            newRoot = allocatePage()
            newRoot.children[0] = root
            splitChild(newRoot, 0)
            root = newRoot

        insertNonFull(root, key, value)

    void insertNonFull(Page* node, Key key, Value value):
        if node.isLeaf():
            // Insert into leaf in sorted order
            node.insertSorted(key, value)
            node.markDirty()
        else:
            // Find child to descend into
            i = node.findChildIndex(key)
            child = node.getChild(i)

            if child.isFull():
                splitChild(node, i)
                if key > node.keys[i]:
                    i++
                child = node.getChild(i)

            insertNonFull(child, key, value)

    void splitChild(Page* parent, int index):
        fullChild = parent.getChild(index)
        newChild = allocatePage()

        // Move half the keys to new child
        midKey = fullChild.keys[T-1]
        newChild.keys = fullChild.keys[T:]
        fullChild.keys = fullChild.keys[:T-1]

        // Insert midKey into parent
        parent.insertKeyAt(index, midKey)
        parent.insertChildAt(index + 1, newChild)

class BufferPool:
    map<PageId, Page*> pages
    list<PageId> lru  // For eviction

    Page* getPage(PageId id):
        if id in pages:
            // Move to front of LRU
            return pages[id]
        else:
            if pages.size() >= MAX_PAGES:
                evictPage()
            page = readPageFromDisk(id)
            pages[id] = page
            return page

    void evictPage():
        victim = lru.back()
        if pages[victim].isDirty():
            writePageToDisk(victim)
        pages.erase(victim)
        lru.pop_back()

Learning milestones

  1. CREATE TABLE and INSERT work → You understand page storage
  2. SELECT with WHERE uses index → You understand B-tree traversal
  3. Large dataset performs well → You understand buffer pool
  4. ROLLBACK undoes changes → You understand transaction isolation
  5. Survives crash → You understand write-ahead logging

Project 9: 2D Game Engine

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: Rust, C Coolness Level: Level 5: Pure Magic Business Potential: 4. The “Open Core” Infrastructure Difficulty: Level 4: Expert Knowledge Area: Graphics / Game Development Software or Tool: SDL2, OpenGL Main Book: “Game Engine Architecture” by Jason Gregory

What you’ll build

A 2D game engine with sprite rendering, animation, physics (collision detection, basic dynamics), input handling, audio, and an entity-component-system (ECS) architecture—capable of running a simple platformer or top-down game.

Why it teaches C++

Game engines demand performance-critical C++. You’ll use cache-friendly data structures (ECS), manage resources (textures, sounds), handle real-time input, and maintain 60 FPS. This is C++ at its most demanding and rewarding.

Core challenges you’ll face

  • Game loop (fixed timestep, interpolation) → maps to frame timing
  • Rendering with SDL2/OpenGL (sprites, batching, shaders) → maps to graphics APIs
  • Entity-Component-System (data-oriented design) → maps to modern game architecture
  • Collision detection (AABB, spatial hashing) → maps to physics algorithms
  • Resource management (loading, caching, unloading) → maps to asset pipeline

Key Concepts

Difficulty: Expert Time estimate: 2-3 months Prerequisites: Projects 1-8, linear algebra basics, graphics concepts

Real world outcome

// Your engine API in action:

class PlatformerGame : public Game {
    Entity player;
    Entity* platforms[10];

    void onCreate() override {
        // Load resources
        auto playerSprite = assets.loadTexture("player.png");
        auto platformSprite = assets.loadTexture("platform.png");

        // Create player entity
        player = world.createEntity();
        player.addComponent<Transform>(100, 100);
        player.addComponent<Sprite>(playerSprite, 32, 32);
        player.addComponent<RigidBody>(1.0f, 0.8f);  // mass, friction
        player.addComponent<BoxCollider>(32, 32);
        player.addComponent<PlayerController>();

        // Create platforms
        for (int i = 0; i < 10; i++) {
            platforms[i] = world.createEntity();
            platforms[i]->addComponent<Transform>(i * 100, 400);
            platforms[i]->addComponent<Sprite>(platformSprite, 100, 20);
            platforms[i]->addComponent<BoxCollider>(100, 20);
            platforms[i]->addComponent<StaticBody>();
        }

        // Add systems
        world.addSystem<PhysicsSystem>();
        world.addSystem<RenderSystem>();
        world.addSystem<AnimationSystem>();
    }

    void onUpdate(float dt) override {
        world.update(dt);
    }
};

int main() {
    Engine engine(800, 600, "My Platformer");
    engine.run<PlatformerGame>();
    return 0;
}

Implementation Hints

Fixed Timestep Game Loop:

const double FIXED_DT = 1.0 / 60.0  // 60 FPS physics
double accumulator = 0.0
double lastTime = getCurrentTime()

while running:
    double currentTime = getCurrentTime()
    double frameTime = currentTime - lastTime
    lastTime = currentTime

    accumulator += frameTime

    // Fixed timestep updates (physics)
    while accumulator >= FIXED_DT:
        world.fixedUpdate(FIXED_DT)
        accumulator -= FIXED_DT

    // Variable timestep updates (rendering)
    double alpha = accumulator / FIXED_DT  // Interpolation factor
    world.render(alpha)

ECS Architecture:

// Components are just data
struct Transform:
    float x, y
    float rotation
    float scaleX, scaleY

struct Velocity:
    float vx, vy

struct Sprite:
    TextureId texture
    int width, height
    int frameX, frameY

// Systems operate on entities with specific components
class MovementSystem : System:
    void update(float dt):
        for entity in world.entitiesWith<Transform, Velocity>():
            Transform& t = entity.get<Transform>()
            Velocity& v = entity.get<Velocity>()
            t.x += v.vx * dt
            t.y += v.vy * dt

class RenderSystem : System:
    void render(float alpha):
        for entity in world.entitiesWith<Transform, Sprite>():
            Transform& t = entity.get<Transform>()
            Sprite& s = entity.get<Sprite>()

            // Interpolate position for smooth rendering
            float renderX = lerp(t.prevX, t.x, alpha)
            float renderY = lerp(t.prevY, t.y, alpha)

            renderer.drawSprite(s.texture, renderX, renderY, s.width, s.height)

AABB Collision:

struct AABB:
    float minX, minY, maxX, maxY

bool intersects(AABB a, AABB b):
    return a.maxX > b.minX && a.minX < b.maxX &&
           a.maxY > b.minY && a.minY < b.maxY

Learning milestones

  1. Window opens and renders sprites → You understand SDL2/OpenGL basics
  2. Player moves with input → You understand input handling
  3. Physics feels right → You understand fixed timestep and integration
  4. Collisions work → You understand AABB detection and response
  5. 60 FPS with 1000 entities → You understand ECS and data-oriented design

Project 10: C++ Compiler Frontend

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: Rust, OCaml Coolness Level: Level 5: Pure Magic Business Potential: 5. The “Industry Disruptor” Difficulty: Level 5: Master Knowledge Area: Compilers / Language Processing Software or Tool: LLVM (optional for codegen) Main Book: “Engineering a Compiler” by Keith D. Cooper

What you’ll build

A compiler frontend for a subset of C++ (or your own language): lexer, parser, AST, semantic analysis (type checking, name resolution), and either an interpreter or LLVM IR code generation.

Why it teaches C++

Building a compiler is the ultimate software engineering challenge. You’ll design complex data structures (AST nodes), implement algorithms (type inference, name lookup), and see how the C++ you write gets transformed into machine code. This project makes you understand C++ at its deepest level.

Core challenges you’ll face

  • Lexical analysis (tokenizing source code) → maps to finite automata
  • Parsing (building AST from tokens) → maps to context-free grammars
  • Symbol tables (tracking declarations, scopes) → maps to name resolution
  • Type checking (type compatibility, inference) → maps to type systems
  • Code generation (LLVM IR or bytecode) → maps to target representation

Key Concepts

  • Lexing and Parsing: “Engineering a Compiler” Chapters 2-4 - Cooper & Torczon
  • Semantic Analysis: “Engineering a Compiler” Chapters 5-7 - Cooper & Torczon
  • Type Systems: “Types and Programming Languages” - Benjamin Pierce
  • LLVM: LLVM Tutorial: Kaleidoscope

Difficulty: Master Time estimate: 3-6 months Prerequisites: All previous projects, theory of computation basics

Real world outcome

$ cat test.my
fn factorial(n: int) -> int {
    if n <= 1 {
        return 1;
    }
    return n * factorial(n - 1);
}

fn main() -> int {
    let result = factorial(5);
    print(result);  // 120
    return 0;
}

$ ./mycompiler test.my
Lexing... 47 tokens
Parsing... AST built
Type checking... OK
Generating LLVM IR...

$ ./mycompiler test.my -o test
$ ./test
120

# Or with interpreter mode:
$ ./mycompiler test.my --interpret
120

Implementation Hints

AST Node Hierarchy (using variant or inheritance):

struct ASTNode { virtual ~ASTNode() = default; }

struct Expr : ASTNode {}
struct IntLiteral : Expr { int value; }
struct BinaryExpr : Expr { unique_ptr<Expr> left, right; BinOp op; }
struct CallExpr : Expr { string callee; vector<unique_ptr<Expr>> args; }
struct VarExpr : Expr { string name; }

struct Stmt : ASTNode {}
struct ReturnStmt : Stmt { unique_ptr<Expr> value; }
struct IfStmt : Stmt { unique_ptr<Expr> cond; unique_ptr<Stmt> then_branch, else_branch; }
struct BlockStmt : Stmt { vector<unique_ptr<Stmt>> statements; }

struct Decl : ASTNode {}
struct FnDecl : Decl { string name; vector<Param> params; Type returnType; unique_ptr<BlockStmt> body; }
struct VarDecl : Decl { string name; Type type; unique_ptr<Expr> init; }

Recursive Descent Parser:

class Parser:
    vector<Token> tokens
    int current = 0

    unique_ptr<Expr> parseExpr():
        return parseEquality()

    unique_ptr<Expr> parseEquality():
        auto left = parseComparison()
        while match(EQUAL_EQUAL, BANG_EQUAL):
            Token op = previous()
            auto right = parseComparison()
            left = make_unique<BinaryExpr>(move(left), op, move(right))
        return left

    // ... more precedence levels

    unique_ptr<Stmt> parseStmt():
        if match(RETURN): return parseReturnStmt()
        if match(IF): return parseIfStmt()
        if match(LET): return parseVarDecl()
        return parseExprStmt()

    unique_ptr<FnDecl> parseFnDecl():
        expect(FN)
        string name = expect(IDENTIFIER).text
        expect(LPAREN)
        vector<Param> params = parseParams()
        expect(RPAREN)
        expect(ARROW)
        Type returnType = parseType()
        auto body = parseBlock()
        return make_unique<FnDecl>(name, move(params), returnType, move(body))

Type Checker:

class TypeChecker:
    SymbolTable symbols

    Type check(Expr* expr):
        if auto* lit = dynamic_cast<IntLiteral*>(expr):
            return Type::Int

        if auto* bin = dynamic_cast<BinaryExpr*>(expr):
            Type left = check(bin->left.get())
            Type right = check(bin->right.get())
            if left != right:
                error("Type mismatch in binary expression")
            return left

        if auto* call = dynamic_cast<CallExpr*>(expr):
            FnDecl* fn = symbols.lookupFn(call->callee)
            if fn->params.size() != call->args.size():
                error("Wrong number of arguments")
            for (int i = 0; i < call->args.size(); i++):
                Type argType = check(call->args[i].get())
                if argType != fn->params[i].type:
                    error("Argument type mismatch")
            return fn->returnType

        if auto* var = dynamic_cast<VarExpr*>(expr):
            return symbols.lookupVar(var->name).type

Learning milestones

  1. Lexer tokenizes correctly → You understand lexical analysis
  2. Parser builds AST → You understand grammars and recursive descent
  3. Type checker catches errors → You understand type systems
  4. Interpreter runs programs → You understand evaluation
  5. LLVM IR generates and runs → You understand code generation

Project 11: HTTP Server Framework

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: Rust, Go Coolness Level: Level 4: Hardcore Tech Flex Business Potential: 4. The “Open Core” Infrastructure Difficulty: Level 4: Expert Knowledge Area: Network / HTTP / Async I/O Software or Tool: HTTP/1.1, epoll/kqueue Main Book: “TCP/IP Illustrated” by W. Richard Stevens

What you’ll build

A high-performance HTTP server framework with routing, middleware, static file serving, and async I/O using epoll/kqueue—capable of handling thousands of concurrent connections.

Why it teaches C++

HTTP servers are the backbone of the web. Building one teaches you protocol parsing, async I/O patterns, and high-performance networking. You’ll learn why frameworks like Nginx and HAProxy are written in C/C++.

Core challenges you’ll face

  • HTTP/1.1 parsing (request line, headers, body, chunked encoding) → maps to protocol parsing
  • Async I/O with epoll/kqueue (non-blocking, event-driven) → maps to I/O multiplexing
  • Routing (matching URLs to handlers, path parameters) → maps to trie or regex matching
  • Middleware (request/response interception) → maps to chain of responsibility
  • Connection management (keep-alive, timeouts) → maps to resource lifecycle

Key Concepts

  • HTTP Protocol: RFC 7230-7235
  • epoll: “The Linux Programming Interface” Chapter 63 - Michael Kerrisk
  • High-Performance Networking: “Systems Performance” by Brendan Gregg
  • Router Design: Trie-based Routing

Difficulty: Expert Time estimate: 1-2 months Prerequisites: Projects 1-8, especially TCP Chat Server

Real world outcome

int main() {
    HttpServer server;

    // Middleware
    server.use([](Request& req, Response& res, Next next) {
        auto start = std::chrono::high_resolution_clock::now();
        next();
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        std::cout << req.method << " " << req.path << " - " << duration.count() << "us\n";
    });

    // Routes
    server.get("/", [](Request& req, Response& res) {
        res.send("Hello, World!");
    });

    server.get("/users/:id", [](Request& req, Response& res) {
        std::string userId = req.params["id"];
        res.json({{"id", userId}, {"name", "Alice"}});
    });

    server.post("/users", [](Request& req, Response& res) {
        auto body = req.json();
        // Create user...
        res.status(201).json({{"id", 1}, {"name", body["name"]}});
    });

    // Static files
    server.serveStatic("/static", "./public");

    server.listen(8080);  // Handles 10k+ concurrent connections
    return 0;
}
$ curl http://localhost:8080/
Hello, World!

$ curl http://localhost:8080/users/42
{"id":"42","name":"Alice"}

$ curl -X POST http://localhost:8080/users -d '{"name":"Bob"}' -H "Content-Type: application/json"
{"id":1,"name":"Bob"}

$ wrk -t12 -c400 -d30s http://localhost:8080/
Running 30s test
  12 threads and 400 connections
  Requests/sec: 125,432.17
  Transfer/sec: 15.23MB

Implementation Hints

Event Loop with epoll:

class EventLoop:
    int epoll_fd
    map<int, Connection*> connections

    void run():
        epoll_fd = epoll_create1(0)

        // Add server socket
        epoll_event ev
        ev.events = EPOLLIN
        ev.data.fd = server_fd
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev)

        epoll_event events[MAX_EVENTS]

        while running:
            int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1)

            for int i = 0; i < n; i++:
                if events[i].data.fd == server_fd:
                    // New connection
                    int client_fd = accept(server_fd, ...)
                    setNonBlocking(client_fd)
                    connections[client_fd] = new Connection(client_fd)

                    ev.events = EPOLLIN | EPOLLET  // Edge-triggered
                    ev.data.fd = client_fd
                    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev)

                else:
                    Connection* conn = connections[events[i].data.fd]
                    if events[i].events & EPOLLIN:
                        conn->handleRead()
                    if events[i].events & EPOLLOUT:
                        conn->handleWrite()

HTTP Request Parser (state machine):

class HttpParser:
    enum State { METHOD, URL, VERSION, HEADER_NAME, HEADER_VALUE, BODY }
    State state = METHOD

    bool parse(const char* data, size_t len):
        for i in 0 to len:
            char c = data[i]
            switch state:
                case METHOD:
                    if c == ' ':
                        request.method = buffer
                        buffer.clear()
                        state = URL
                    else:
                        buffer += c
                    break

                case URL:
                    if c == ' ':
                        request.path = buffer
                        buffer.clear()
                        state = VERSION
                    else:
                        buffer += c
                    break

                // ... more states

Learning milestones

  1. Handles basic GET requests → You understand HTTP parsing
  2. Routing works → You understand URL matching
  3. Handles 1000+ concurrent connections → You understand epoll/kqueue
  4. Middleware chains work → You understand composition
  5. Benchmark shows high throughput → You understand optimization

Project 12: Template Metaprogramming Library

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ Alternative Programming Languages: D, Rust (const generics) Coolness Level: Level 5: Pure Magic Business Potential: 1. The “Resume Gold” Difficulty: Level 5: Master Knowledge Area: Templates / Metaprogramming Software or Tool: C++ Templates Main Book: “C++ Templates: The Complete Guide” by Vandevoorde & Josuttis

What you’ll build

A template metaprogramming library with compile-time type lists, type traits, compile-time string manipulation, and a simple compile-time JSON parser—all computed at compile time with zero runtime cost.

Why it teaches C++

Template metaprogramming is C++’s dark magic. You’ll learn to compute at compile time, create zero-cost abstractions, and understand how libraries like Boost and the STL achieve their magic. C++20 concepts and constexpr make this more accessible than ever.

Core challenges you’ll face

  • Type lists (storing/manipulating types at compile time) → maps to variadic templates
  • Type traits (querying properties of types) → maps to SFINAE and concepts
  • Compile-time computation (constexpr, consteval) → maps to constant evaluation
  • Compile-time strings (fixed_string, string literals) → maps to non-type template parameters
  • Recursive template instantiation (compile-time loops) → maps to template recursion

Key Concepts

  • Variadic Templates: “C++ Templates: The Complete Guide” Chapter 4 - Vandevoorde et al.
  • SFINAE: “C++ Templates: The Complete Guide” Chapter 8 - Vandevoorde et al.
  • Concepts: “C++20 - The Complete Guide” Chapter 5 - Nicolai Josuttis
  • constexpr: “Effective Modern C++” Item 15 - Scott Meyers

Difficulty: Master Time estimate: 1-2 months Prerequisites: All previous projects, template basics

Real world outcome

// Type list operations
using MyTypes = TypeList<int, double, std::string>;
static_assert(Length<MyTypes>::value == 3);
static_assert(std::is_same_v<At<MyTypes, 0>, int>);
static_assert(Contains<MyTypes, double>::value);
static_assert(IndexOf<MyTypes, std::string>::value == 2);

using Filtered = Filter<MyTypes, std::is_arithmetic>;  // TypeList<int, double>
using Transformed = Transform<MyTypes, std::add_pointer>;  // TypeList<int*, double*, std::string*>

// Compile-time string
constexpr FixedString hello = "Hello";
constexpr FixedString world = "World";
constexpr auto greeting = hello + ", " + world + "!";
static_assert(greeting == "Hello, World!");

// Compile-time JSON parsing
constexpr auto json = R"({"name": "Alice", "age": 30})"_json;
static_assert(json["name"] == "Alice");
static_assert(json["age"] == 30);

// Type-safe printf format checking
print<"Hello, %s! You are %d years old.">("Alice", 30);  // OK
// print<"Hello, %s!">(42);  // Compile error: expected string, got int

// Reflection-like serialization
struct Person {
    std::string name;
    int age;
    REFLECT(name, age)
};

constexpr auto json_str = to_json(Person{"Bob", 25});
// json_str is computed at compile time!

Implementation Hints

Type List:

template<typename... Ts>
struct TypeList {};

// Length
template<typename List>
struct Length;

template<typename... Ts>
struct Length<TypeList<Ts...>> {
    static constexpr size_t value = sizeof...(Ts);
};

// At (get type at index)
template<typename List, size_t Index>
struct AtImpl;

template<typename Head, typename... Tail>
struct AtImpl<TypeList<Head, Tail...>, 0> {
    using type = Head;
};

template<typename Head, typename... Tail, size_t Index>
struct AtImpl<TypeList<Head, Tail...>, Index> {
    using type = typename AtImpl<TypeList<Tail...>, Index - 1>::type;
};

template<typename List, size_t Index>
using At = typename AtImpl<List, Index>::type;

// Contains
template<typename List, typename T>
struct Contains;

template<typename T>
struct Contains<TypeList<>, T> : std::false_type {};

template<typename Head, typename... Tail, typename T>
struct Contains<TypeList<Head, Tail...>, T>
    : std::conditional_t<std::is_same_v<Head, T>,
                         std::true_type,
                         Contains<TypeList<Tail...>, T>> {};

// Filter (keep types matching predicate)
template<typename List, template<typename> class Pred>
struct Filter;

template<template<typename> class Pred>
struct Filter<TypeList<>, Pred> {
    using type = TypeList<>;
};

template<typename Head, typename... Tail, template<typename> class Pred>
struct Filter<TypeList<Head, Tail...>, Pred> {
    using rest = typename Filter<TypeList<Tail...>, Pred>::type;
    using type = std::conditional_t<
        Pred<Head>::value,
        typename Prepend<Head, rest>::type,
        rest
    >;
};

Compile-time String:

template<size_t N>
struct FixedString {
    char data[N];

    constexpr FixedString(const char (&str)[N]) {
        for (size_t i = 0; i < N; i++) {
            data[i] = str[i];
        }
    }

    constexpr size_t size() const { return N - 1; }  // Exclude null terminator
    constexpr char operator[](size_t i) const { return data[i]; }

    template<size_t M>
    constexpr auto operator+(const FixedString<M>& other) const {
        char result[N + M - 1];
        for (size_t i = 0; i < N - 1; i++) result[i] = data[i];
        for (size_t i = 0; i < M; i++) result[N - 1 + i] = other.data[i];
        return FixedString<N + M - 1>(result);
    }
};

template<size_t N>
FixedString(const char (&)[N]) -> FixedString<N>;

Learning milestones

  1. TypeList operations work → You understand variadic templates
  2. Type traits work → You understand SFINAE/concepts
  3. Compile-time string works → You understand NTTP (non-type template parameters)
  4. Compile-time JSON parses → You understand constexpr evaluation
  5. Everything computed at compile time → You understand zero-cost abstractions

Project Comparison Table

Project Difficulty Time Depth of Understanding Fun Factor Business Potential
1. Expression Calculator Beginner 1-2 weeks ⭐⭐⭐ ⭐⭐⭐ Resume Gold
2. Memory-Mapped File Reader Intermediate 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐ Micro-SaaS
3. Smart Pointer Implementation Advanced 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ Resume Gold
4. Generic Container Library Advanced 3-4 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐ Resume Gold
5. Thread Pool Library Advanced 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ Service & Support
6. JSON Parser Intermediate 2-3 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐ Service & Support
7. TCP Chat Server Advanced 2-3 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ Micro-SaaS
8. Database Engine Expert 1-2 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ Open Core
9. 2D Game Engine Expert 2-3 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ Open Core
10. Compiler Frontend Master 3-6 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ Industry Disruptor
11. HTTP Server Framework Expert 1-2 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ Open Core
12. Template Metaprogramming Master 1-2 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ Resume Gold

Phase 1: Foundation (1-2 months)

Complete Projects 1-2 to understand:

  • Basic C++ syntax and idioms
  • String manipulation and parsing
  • File I/O and system calls
  • Memory layout basics

Phase 2: Core C++ (2-3 months)

Complete Projects 3-5 to understand:

  • RAII and ownership semantics
  • Move semantics deeply
  • Templates and generic programming
  • Concurrency primitives

Phase 3: Practical Applications (2-3 months)

Complete Projects 6-7 to apply your knowledge:

  • Real-world parsing (JSON)
  • Network programming
  • Protocol design
  • Multi-client handling

Phase 4: Systems Mastery (3-6 months)

Complete Projects 8-11 for advanced systems:

  • Storage engines and databases
  • Game engines and real-time systems
  • Compilers and language processing
  • High-performance networking

Phase 5: Expert Level (1-2 months)

Complete Project 12 for:

  • Template metaprogramming
  • Compile-time computation
  • Zero-cost abstractions

Final Capstone Project: Operating System Kernel

File: CPP_MASTERY_PROJECTS.md Main Programming Language: C++ (with C and Assembly) Alternative Programming Languages: Rust, C Coolness Level: Level 5: Pure Magic Business Potential: 5. The “Industry Disruptor” Difficulty: Level 5: Master Knowledge Area: Operating Systems / Bare Metal Software or Tool: QEMU, x86-64 or ARM Main Book: “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau

What you’ll build

A minimal operating system kernel that boots, sets up memory management (paging), handles interrupts, supports basic scheduling (round-robin), has a simple filesystem, and can run user-space programs.

Why it teaches C++

Writing an OS kernel is the ultimate test of your understanding. You’re writing code that runs with no safety net—no standard library, no operating system, just you and the hardware. You’ll use C++ in ways you never imagined, from placement new to custom allocators to inline assembly.

Core challenges you’ll face

  • Bare-metal boot (bootloader, protected/long mode) → maps to x86 architecture
  • Memory management (paging, virtual memory, heap) → maps to memory systems
  • Interrupt handling (IDT, IRQ, syscalls) → maps to hardware interrupts
  • Process scheduling (context switching, scheduler) → maps to OS concepts
  • Filesystem (block device, inode, directories) → maps to storage systems

Key Concepts

  • OS Concepts: “Operating Systems: Three Easy Pieces” - Arpaci-Dusseau
  • x86 Assembly: “Low-Level Programming” by Igor Zhirkov
  • Kernel Development: OSDev Wiki
  • Memory Management: “Computer Systems: A Programmer’s Perspective” Chapter 9

Difficulty: Master Time estimate: 6-12 months Prerequisites: All previous projects, assembly basics, computer architecture

Real world outcome

$ make run
Booting MyOS...
[  0.000] Kernel loaded at 0x100000
[  0.001] Setting up GDT... OK
[  0.002] Setting up IDT... OK
[  0.003] Enabling paging... OK
[  0.004] Initializing heap... 128 MB available
[  0.005] Initializing scheduler... OK
[  0.006] Mounting root filesystem... OK
[  0.010] Starting init process...

Welcome to MyOS!
$ ls
bin  etc  home  usr
$ cat /etc/hostname
myos
$ ps
PID  STATE    CMD
  1  RUNNING  init
  2  RUNNING  shell
$ ./hello
Hello from userspace!
$ fork_test
Parent PID: 3
Child PID: 4
Fork works!

Implementation Hints

Multiboot Header (for GRUB):

section .multiboot
    dd 0x1BADB002       ; Magic number
    dd 0x00             ; Flags
    dd -(0x1BADB002)    ; Checksum

Entering C++ from Assembly:

section .text
global _start
extern kernel_main

_start:
    ; Set up stack
    mov esp, stack_top

    ; Call C++ kernel
    call kernel_main

    ; Halt if kernel returns
    cli
    hlt

section .bss
stack_bottom:
    resb 16384
stack_top:

Kernel Main:

// No standard library! Write everything yourself.
#include "terminal.h"
#include "gdt.h"
#include "idt.h"
#include "paging.h"
#include "heap.h"
#include "scheduler.h"

extern "C" void kernel_main() {
    Terminal::initialize();
    Terminal::println("Kernel loaded");

    GDT::initialize();
    Terminal::println("GDT initialized");

    IDT::initialize();
    Terminal::println("IDT initialized");

    Paging::initialize();
    Terminal::println("Paging enabled");

    Heap::initialize(128 * 1024 * 1024);  // 128 MB
    Terminal::println("Heap initialized");

    Scheduler::initialize();
    Terminal::println("Scheduler initialized");

    // Create init process
    Process* init = Scheduler::create_process(init_main);
    Scheduler::run();
}

Custom Allocators (since there’s no malloc):

void* operator new(size_t size) {
    return Heap::allocate(size);
}

void* operator new[](size_t size) {
    return Heap::allocate(size);
}

void operator delete(void* ptr) noexcept {
    Heap::free(ptr);
}

void operator delete[](void* ptr) noexcept {
    Heap::free(ptr);
}

// Placement new (always available)
inline void* operator new(size_t, void* ptr) noexcept {
    return ptr;
}

Learning milestones

  1. Kernel boots and prints → You understand boot process
  2. Paging works → You understand virtual memory
  3. Interrupts handled → You understand hardware interaction
  4. Multiple processes run → You understand scheduling
  5. User programs execute → You understand protection rings

Essential Resources

Official Documentation

Books (from your collection)

  • “C++ Primer Plus” by Stephen Prata - Comprehensive beginner book
  • “The C++ Programming Language” by Bjarne Stroustrup - The authoritative reference
  • “A Tour of C++” by Bjarne Stroustrup - Quick overview of modern C++
  • “Programming: Principles and Practice Using C++” by Bjarne Stroustrup - Learning programming with C++
  • “Effective Modern C++” by Scott Meyers - C++11/14 best practices
  • “C++ Concurrency in Action” by Anthony Williams - Threading and concurrency
  • “C++ Templates: The Complete Guide” by Vandevoorde & Josuttis - Templates mastery

Online Resources


Summary

# Project Main Language
1 Command-Line Calculator with Expression Parser C++
2 Memory-Mapped File Reader C++
3 Smart Pointer Implementation C++
4 Generic Container Library C++
5 Thread Pool Library C++
6 JSON Parser Library C++
7 TCP Chat Server C++
8 Simple Database Engine C++
9 2D Game Engine C++
10 C++ Compiler Frontend C++
11 HTTP Server Framework C++
12 Template Metaprogramming Library C++
Final Operating System Kernel C++ (with C and Assembly)

This learning path takes you from basic syntax to writing operating systems. C++ is a journey, not a destination. Each project builds on the previous, and by the end, you’ll understand not just how to use C++, but why it’s designed the way it is.