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:
Internalize ownership. Never again write ambiguous “who deletes this?” code. Every heap allocation has an owner expressed in the type system.
Understand values, references, and moves at the level of
xvaluevsprvalue. Not memorized — understood. Once. Correctly.Read and write templates without fear, including C++20 concepts and constraint composition.
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.
Handle errors deliberately — knowing when exceptions win, when
std::expectedwins, and why Google-style codebases ban exceptions altogether.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:
Write a class that manages a resource with correct move constructor, move assignment, destructor, and rule-of-zero variant. Explain when each is appropriate.
Decide between
unique_ptr,shared_ptr,weak_ptr, and raw non-owning pointer for a given ownership scenario in under 10 seconds.Explain why
std::move(x)does not move anything, and what a “cast to xvalue” means.Explain guaranteed copy elision (C++17) and why
return T{}never calls a move constructor since 2017.Distinguish
T&&in a template deduction context (forwarding reference) fromT&&in a concrete function (rvalue reference), and usestd::forward<T>correctly.Write a function template constrained by a C++20 concept, and define a custom concept using
requiresexpressions.Read a 400-line template error and locate the actual failure line within 90 seconds.
Choose between
std::vector,std::deque,std::list,std::unordered_map,std::map, andstd::flat_map(C++23) based on access pattern and complexity requirements — and defend the choice.State the iterator invalidation rules for
vector::push_back,vector::insert,unordered_map::insert, andlist::insert.Write a data-processing pipeline using
std::ranges::views(filter | transform) and materialize it withranges::to<std::vector>(C++23).Explain when
std::string_viewis safe and when it dangles. Know at least three ways to accidentally create a danglingstring_view.Choose between throwing an exception, returning
std::expected<T, E>, and returningstd::optional<T>based on whether the failure is expected, exceptional, or “value or nothing.”Mark a function
noexceptcorrectly and explain whystd::vector<T>::push_backcares whether your move constructor isnoexcept.Build the LRU cache, JSON parser, and object pool in this phase and pass their acceptance criteria under
-fsanitize=address,undefined.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
autoand lambdas sprinkled in.” They keep rawnew/delete, keep passing everything byconst 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 wroteauto x = get_tensor()whereget_tensorreturns byconst&.The fix: Ownership first, values second, references only when you have a reason. Write
std::unique_ptr<T>return types. WriteTparameter types when the callee needs its own copy and let the caller decide whether tostd::move. Writestd::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 smallResourceclass 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_ptrandshared_ptrownership, and you know whyshared_ptrcosts ~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 onstd::list<std::pair<K,V>>+std::unordered_map<K, iterator>— the classic O(1) trick.Build (6-8h): See
projects.mdfor 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,undefinedclean.
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
Stringclass with rule-of-five, then compare against rule-of-zero usingstd::stringinternally. Instrument moves. Write aperfect_forward_wrapperfor a factory function.Weekend (2-3h): Look at three real bugs from open GitHub issues where
std::movewas misused (search:std::move const). Understand each one.Exit check: You can explain, out loud, what happens to
s2instd::string s1 = "hello"; std::string s2 = std::move(s1);— including the guaranteed post-condition ons1.
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. Usestd::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
forloops. Rewrite it three ways: withstd::transform/std::accumulate; withstd::ranges::viewspipeline; withranges::to<std::vector>(C++23). Compare readability and codegen on godbolt.org.Exit check: You automatically reach for
std::find_ifinstead of writing a loop. When you seefor (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. Usestd::mutex+ RAII checkout guard. Benchmark against directnew/deletefor 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 |
|---|---|
RAII, smart pointers, ownership decisions |
|
lvalue/rvalue/xvalue/prvalue, |
|
Function/class templates, C++20 concepts, CRTP, |
|
Containers, algorithms, iterators, ranges, complexity, invalidation |
|
Exceptions, |
|
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 upstreamllvm@18or newer via Homebrew:brew install llvm && export PATH="/opt/homebrew/opt/llvm/bin:$PATH".Standard:
-std=c++20by default.-std=c++2bwhen you needstd::expectedand friends.Warnings: Always compile with
-Wall -Wextra -Wpedantic -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Woverloaded-virtual -Wconversion.Sanitizers:
-fsanitize=address,undefinedon every debug build.-fsanitize=threadfor the object pool project.Build: CMake ≥ 3.25. Learn
target_compile_features(mytarget PUBLIC cxx_std_20)— never setCMAKE_CXX_STANDARDglobally.Format:
.clang-formatcopied 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:
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.
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.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.
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:
using namespace std;at file scope in headers. It leaks the entire STL into every translation unit that includes you. In a.cppfor a small program, it’s tolerable. In a header, it’s a bug.Raw
newanddeletein application code. Every raw allocation is a leak waiting to happen. Wrap it inunique_ptror use a container.std::endlin every log line. It flushes the stream, which is a syscall. Use'\n'and let the stream buffer.C-style casts
(int)x. Usestatic_cast<int>(x), orreinterpret_castif you’re doing bit-level surgery. C casts silently do the most powerful cast that compiles, which is usually not what you want.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.std::shared_ptras the default. Reach forunique_ptrfirst. Reach forshared_ptronly when you can name two independent owners.Manual loops where an algorithm exists.
for (auto& x : v) if (pred(x)) result.push_back(x);isstd::copy_if. Say what you mean.typedeffor aliases. Useusing. It reads left-to-right and works with templates.NULL. Usenullptr. It’s not the same.NULLis an integer,nullptris a pointer.Owning raw pointers as class members without a destructor plan. Wrap in
unique_ptrand 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::mutexandstd::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.orguse. Phase 4.Metaprogramming beyond concepts +
if constexpr. Noboost::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