Phase 1 — Modern C++ Core (Months 2-3, Weeks 5-12)

“Modern C++ is a different language from C-with-classes. The syntax overlaps. The mental model does not.”

You wrote C++ two years ago. You wrote it in college before that. Some of what you knew is still true. A surprising amount is either wrong, obsolete, or actively harmful in a codebase written after 2017. This phase does not teach you C++. It teaches you the version of C++ that enterprise teams — Bloomberg, Google, Nvidia, Adobe, and yes, Zoho’s platform teams — actually ship in 2026.

The thesis is simple. Modern C++ (C++17/20/23) is organized around three ideas that pre-2011 C++ did not have: value semantics with cheap moves, RAII enforced by the type system through smart pointers, and compile-time constraints expressed via templates and concepts. Everything else — ranges, std::expected, constexpr everything, structured bindings — is a consequence of those three. Miss the three and you will forever be a C-with-classes programmer typing more angle brackets.

By the end of Week 12 you will not “know” modern C++. You will reach for it. Move constructors, std::unique_ptr<T> return types, std::span<const T> parameters, and range-based algorithms will be your defaults, and raw new/delete will feel physically wrong in your hands.


Mission

Rebuild your C++ instincts around the modern idioms an applied ML engineer at an MNC will actually encounter reading TensorFlow internals, ONNX Runtime, or Zoho’s own C++ backend services. You are not becoming a language lawyer. You are becoming fluent — the kind of fluent where you read a 30-line function with unique_ptr, std::optional, and a ranges pipeline and understand it before you finish scrolling.

Concretely, you will:

  1. Internalize ownership. Never again write ambiguous “who deletes this?” code. Every heap allocation has an owner expressed in the type system.

  2. Understand values, references, and moves at the level of xvalue vs prvalue. Not memorized — understood. Once. Correctly.

  3. Read and write templates without fear, including C++20 concepts and constraint composition.

  4. Use the STL as a toolbox, not a museum, knowing the complexity, iterator invalidation, and idiomatic use of the ~10 containers and ~20 algorithms you’ll actually reach for.

  5. Handle errors deliberately — knowing when exceptions win, when std::expected wins, and why Google-style codebases ban exceptions altogether.

  6. Ship three real projects: a templated LRU cache, a from-scratch JSON parser, and a thread-safe object pool with real benchmarks.


Exit Criteria (15 items)

You do not leave Phase 1 until you can, without googling:

  1. Write a class that manages a resource with correct move constructor, move assignment, destructor, and rule-of-zero variant. Explain when each is appropriate.

  2. Decide between unique_ptr, shared_ptr, weak_ptr, and raw non-owning pointer for a given ownership scenario in under 10 seconds.

  3. Explain why std::move(x) does not move anything, and what a “cast to xvalue” means.

  4. Explain guaranteed copy elision (C++17) and why return T{} never calls a move constructor since 2017.

  5. Distinguish T&& in a template deduction context (forwarding reference) from T&& in a concrete function (rvalue reference), and use std::forward<T> correctly.

  6. Write a function template constrained by a C++20 concept, and define a custom concept using requires expressions.

  7. Read a 400-line template error and locate the actual failure line within 90 seconds.

  8. Choose between std::vector, std::deque, std::list, std::unordered_map, std::map, and std::flat_map (C++23) based on access pattern and complexity requirements — and defend the choice.

  9. State the iterator invalidation rules for vector::push_back, vector::insert, unordered_map::insert, and list::insert.

  10. Write a data-processing pipeline using std::ranges::views (filter | transform) and materialize it with ranges::to<std::vector> (C++23).

  11. Explain when std::string_view is safe and when it dangles. Know at least three ways to accidentally create a dangling string_view.

  12. Choose between throwing an exception, returning std::expected<T, E>, and returning std::optional<T> based on whether the failure is expected, exceptional, or “value or nothing.”

  13. Mark a function noexcept correctly and explain why std::vector<T>::push_back cares whether your move constructor is noexcept.

  14. Build the LRU cache, JSON parser, and object pool in this phase and pass their acceptance criteria under -fsanitize=address,undefined.

  15. Read the first 200 lines of <vector> or <optional> from libc++ and understand the shape of the implementation, even if not every detail.


What Most People Get Wrong

The trap: Treating modern C++ as “C++ with auto and lambdas sprinkled in.” They keep raw new/delete, keep passing everything by const T& because “references are fast,” and never reason about ownership. Their code compiles, runs, and looks modern. It also leaks, double-frees under exceptions, and copies a 40MB tensor because they wrote auto x = get_tensor() where get_tensor returns by const&.

The fix: Ownership first, values second, references only when you have a reason. Write std::unique_ptr<T> return types. Write T parameter types when the callee needs its own copy and let the caller decide whether to std::move. Write std::span<const T> when you need a non-owning view of contiguous data. References become the exception, not the default.

Every senior C++ engineer you’ll ever meet crossed this bridge. Most of them crossed it painfully. You get to cross it deliberately in the next eight weeks.


Week-by-Week Breakdown (W5–W12)

Budget: 10–15 hrs/week. Weeknights are for reading and small exercises. Weekends are for the project work.

W5 — Ownership & RAII

  • Reading (4-5h): 01_ownership_and_raii.md. Scott Meyers Effective Modern C++ Items 18–22. Herb Sutter’s GotW #89 & #91.

  • Exercises (3-4h): Rewrite three heap-allocating C-style functions to return std::unique_ptr<T>. Build a small Resource class that logs construct/copy/move/destroy and observe what actually happens under different call patterns.

  • Weekend (3-4h): Watch Herb Sutter “Leak-Freedom in C++” (CppCon). Sketch the memory ownership diagram for the LRU cache you’ll write in W6.

  • Exit check: You can defend, on a whiteboard, the difference between unique_ptr and shared_ptr ownership, and you know why shared_ptr costs ~16 bytes plus a control block plus two atomic refcounts.

W6 — Project P1.1: Templated LRU Cache

  • Design (2h): Sketch the API. template<class K, class V> class LRUCache { void put(K, V); std::optional<V> get(const K&); };. Decide on std::list<std::pair<K,V>> + std::unordered_map<K, iterator> — the classic O(1) trick.

  • Build (6-8h): See projects.md for full acceptance criteria.

  • Test (2-3h): Write unit tests with GoogleTest or Catch2. Benchmark 1M ops.

  • Exit check: Green tests, benchmark under 500ms, -fsanitize=address,undefined clean.

W7 — Move Semantics & Value Categories

  • Reading (5-6h): 02_move_semantics_and_value_categories.md. Scott Meyers Items 23–30. Read Howard Hinnant’s “Value Categories” article twice.

  • Exercises (3-4h): Implement a String class with rule-of-five, then compare against rule-of-zero using std::string internally. Instrument moves. Write a perfect_forward_wrapper for a factory function.

  • Weekend (2-3h): Look at three real bugs from open GitHub issues where std::move was misused (search: std::move const). Understand each one.

  • Exit check: You can explain, out loud, what happens to s2 in std::string s1 = "hello"; std::string s2 = std::move(s1); — including the guaranteed post-condition on s1.

W8-W9 — Project P1.2: JSON Parser + Templates & Concepts

  • W8 Reading (4-5h): 03_templates_and_concepts.md. Chapter 1-3 of Vandevoorde/Josuttis C++ Templates: The Complete Guide, 2nd ed (skim, not memorize). Andreas Fertig’s C++20 concepts posts.

  • W8-W9 Build (10-12h across both weeks): Recursive-descent JSON parser. See projects.md. Use std::variant<null_t, bool, double, std::string, array, object> for values. Practice concepts by constraining any generic serialization helpers.

  • Exit check: 10 test JSONs parse; error messages report line:col; no external dependencies; all code compiles under -Wall -Wextra -Wpedantic -std=c++20.

W10 — STL Deep Dive

  • Reading (5-6h): 04_stl_deep_dive.md. Skim cppreference for the 20 algorithms listed there. Watch Sean Parent’s “C++ Seasoning” talk — the one where he says “no raw loops.”

  • Exercises (4-5h): Take a piece of ML preprocessing code (mock a feature-vector transform) written with raw for loops. Rewrite it three ways: with std::transform/std::accumulate; with std::ranges::views pipeline; with ranges::to<std::vector> (C++23). Compare readability and codegen on godbolt.org.

  • Exit check: You automatically reach for std::find_if instead of writing a loop. When you see for (auto& x : vec) { if (pred(x)) result.push_back(f(x)); } you feel a small physical discomfort.

W11-W12 — Project P1.3: Object Pool + Error Handling

  • W11 Reading (3-4h): 05_error_handling_modern.md. Google C++ Style Guide’s exceptions section (read once, understand why, not just the rule). Herb Sutter’s P0709 “Zero-overhead deterministic exceptions” (even though not standardized — the design analysis is educational).

  • W11-W12 Build (10-12h): Thread-safe templated object pool. See projects.md. Use std::mutex + RAII checkout guard. Benchmark against direct new/delete for 10K allocations of a 1KB payload.

  • Exit check: 2x+ speedup, ASan+UBSan+TSan clean, README with benchmark chart, published to GitHub.


Files in This Phase

File

Purpose

01_ownership_and_raii.md

RAII, smart pointers, ownership decisions

02_move_semantics_and_value_categories.md

lvalue/rvalue/xvalue/prvalue, std::move, std::forward, elision

03_templates_and_concepts.md

Function/class templates, C++20 concepts, CRTP, if constexpr

04_stl_deep_dive.md

Containers, algorithms, iterators, ranges, complexity, invalidation

05_error_handling_modern.md

Exceptions, noexcept, std::expected (C++23), std::optional, std::variant

projects.md

LRU cache, JSON parser, object pool — full specs


Toolchain Baseline (macOS Apple Silicon)

  • Compiler: clang++ from Xcode Command Line Tools (defaults to a recent Apple Clang, supports C++20 and most of C++23). For C++23 features not in Apple Clang (std::expected, std::flat_map, std::print), install upstream llvm@18 or newer via Homebrew: brew install llvm && export PATH="/opt/homebrew/opt/llvm/bin:$PATH".

  • Standard: -std=c++20 by default. -std=c++2b when you need std::expected and friends.

  • Warnings: Always compile with -Wall -Wextra -Wpedantic -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Woverloaded-virtual -Wconversion.

  • Sanitizers: -fsanitize=address,undefined on every debug build. -fsanitize=thread for the object pool project.

  • Build: CMake ≥ 3.25. Learn target_compile_features(mytarget PUBLIC cxx_std_20) — never set CMAKE_CXX_STANDARD globally.

  • Format: .clang-format copied from LLVM style, then tune.


Success Metric

At the end of W12, do this exercise. Open any file in the LLVM libc++ source tree — pick optional, expected, or unique_ptr. Read the first 200 lines. If you can follow the shape of the code — even if some SFINAE tricks or __ internal names are unfamiliar — you have graduated Phase 1. If it still looks like alien hieroglyphics, spend another week on templates and re-read 03_templates_and_concepts.md.

Do not skip this metric. It’s the only honest test.


Learning Philosophy for Phase 1

You are returning to a language you used two years ago in a codebase that has since added three major standard revisions. Two failure modes will tempt you. The first is nostalgia — writing C-with-classes because it’s familiar. The second is over-reading — spending the whole phase on cppreference and never shipping code. Both fail.

The cure is a strict weekly loop:

  1. Monday–Tuesday: read. 2–3 hours across a single topic. Read the file in this phase, plus one talk or blog post. Take notes in your own words.

  2. Wednesday–Thursday: type it. Rewrite the examples yourself. Do not paste. The muscle memory of typing std::unique_ptr<Model> m = std::make_unique<Model>(cfg); matters more than reading it fifty times.

  3. Friday: instrument. Add print statements to constructors, copy, move, destructor. Run the code. Watch what actually happens under different call patterns. Modern C++ is invisible unless you look.

  4. Weekend: build. The project of the week or a small standalone exercise. Deliverable-driven. If you produce code, you learned. If you produce notes, you’re stalling.

If a week ends without executable code, you didn’t do Phase 1 that week. Reset and go again.


Anti-Patterns to Delete from Your Instincts

You spent your undergraduate years learning C++ patterns that are actively wrong in 2026. Delete these on sight:

  1. using namespace std; at file scope in headers. It leaks the entire STL into every translation unit that includes you. In a .cpp for a small program, it’s tolerable. In a header, it’s a bug.

  2. Raw new and delete in application code. Every raw allocation is a leak waiting to happen. Wrap it in unique_ptr or use a container.

  3. std::endl in every log line. It flushes the stream, which is a syscall. Use '\n' and let the stream buffer.

  4. C-style casts (int)x. Use static_cast<int>(x), or reinterpret_cast if you’re doing bit-level surgery. C casts silently do the most powerful cast that compiles, which is usually not what you want.

  5. Passing everything by const T& because “references are fast.” If the callee needs its own copy, pass by value and let it move. If the callee only reads, const T& is right — but for cheap-to-copy types (int, std::string_view, small structs), pass by value.

  6. std::shared_ptr as the default. Reach for unique_ptr first. Reach for shared_ptr only when you can name two independent owners.

  7. Manual loops where an algorithm exists. for (auto& x : v) if (pred(x)) result.push_back(x); is std::copy_if. Say what you mean.

  8. typedef for aliases. Use using. It reads left-to-right and works with templates.

  9. NULL. Use nullptr. It’s not the same. NULL is an integer, nullptr is a pointer.

  10. Owning raw pointers as class members without a destructor plan. Wrap in unique_ptr and get rule-of-zero for free.

Every one of these appears in code that compiles, runs, and passes review at less careful shops. In a modern applied-ML C++ codebase, they mark you as someone who hasn’t caught up.


Compiler and Sanitizer Configuration

Make one project-level cmake/warnings.cmake file and reuse it across all three Phase 1 projects. Contents:

add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
    -Wall -Wextra -Wpedantic
    -Wshadow -Wnon-virtual-dtor -Wold-style-cast
    -Wcast-align -Woverloaded-virtual -Wconversion
    -Wsign-conversion -Wnull-dereference -Wdouble-promotion
    -Wformat=2 -Wimplicit-fallthrough
    $<$<CXX_COMPILER_ID:GNU>:-Wuseless-cast -Wsuggest-override>
    $<$<CXX_COMPILER_ID:Clang>:-Wpessimizing-move -Wself-assign>
)

add_library(project_sanitizers INTERFACE)
target_compile_options(project_sanitizers INTERFACE
    -fsanitize=address,undefined -fno-omit-frame-pointer -g
)
target_link_options(project_sanitizers INTERFACE
    -fsanitize=address,undefined
)

Link every target against project_warnings. Link debug builds against project_sanitizers too. Never suppress a warning by adding -Wno-*; fix the underlying code. If you truly must suppress, do it locally with a pragma and a comment explaining why.

Run tests under sanitizers before every commit. Not “eventually.” Every commit. The habit is what makes shipping code correct on the first attempt possible.


What You Will Not Learn in Phase 1

This phase is intentionally scoped. Things deliberately deferred to later phases:

  • Concurrency primitives beyond std::mutex and std::condition_variable. Atomics, memory ordering, lock-free structures come in Phase 3.

  • Modules. C++20 modules are still tooling-immature on macOS + CMake in 2026. Learn the concept, don’t rely on them yet.

  • Coroutines. C++20 coroutines are powerful but niche. Phase 4 territory.

  • Networking / async I/O. No boost::asio, no networking TS. Phase 3.

  • Build systems beyond basic CMake. Bazel, Meson, package managers (Conan, vcpkg). Phase 2 covers this.

  • Compiler internals and codegen inspection beyond casual godbolt.org use. Phase 4.

  • Metaprogramming beyond concepts + if constexpr. No boost::mp11, no Hana. Save for Phase 5 if you want it.

Stay in scope. Discipline is what turns 13 months into mastery instead of 13 months of dabbling.


Weekly Ceremony

At the end of each week, write a 200-word entry in ~/logs/cpp/phase1-wN.md:

  • What did I read this week?

  • What did I write?

  • What surprised me?

  • What is still confusing?

  • What is my confidence on the exit criteria items covered this week (0–10)?

This log serves two purposes. First, it forces reflection, which turns fragmented exposure into stable knowledge. Second, in six months when you’re deep in Phase 3 and someone asks you a question about move semantics, you can search your own logs faster than re-reading Meyers. Keep the log.


Return to Master README · Next: 01_ownership_and_raii.md