Learn modern C++ by reading and running code, not slides. 32 self-contained projects take you from the classical (pre-C++11) foundation to C++23, with guarded C++26 previews — plus design patterns, architecture patterns, data structures, static analysis and a Qt 6 tour.
In every project the code is the course material: self-documenting names first, comments where they add insight, and every claim measured rather than asserted. Projects 01–31 are console programs; 32_qt_samples adds seven Qt 6 applications (screenshots).
| You are | Start at |
|---|---|
| New to C++ syntax | 01_classical_cpp — reading_declarations.cpp decodes declarations token by token |
| Coming from C++98/03 | 02_cpp11_language → 03_cpp11_move_smart_pointers |
| Refreshing on C++17/20/23 | 05_cpp17 → 06_cpp20 → 07_cpp23 |
| Here for tooling / static analysis | 18_misra_deviations, 23_frontend_conformance, axivion/ |
| Here for Qt | 31_qt_bridge → 32_qt_samples |
Prerequisites: a C++20-capable compiler (GCC 13+, Clang 18+, or MSVC 19.35+/VS 2022 17.5+), CMake 3.21+, and Ninja or Make. Two projects ask for more and skip themselves otherwise: 29_modules needs CMake 3.28+ with a modules-capable compiler, 31_qt_bridge needs Qt 6 Core and 32_qt_samples needs Qt 6.8+. Everything else builds without Qt at all.
git clone https://github.com/MartinSch77/ModernCppTraining.git
cd ModernCppTraining
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/01_classical_cpp/01_classical_cpp # start here
ctest --test-dir build # 30 tests: every sample builds AND runsEach project is independent — build only the one you want:
cmake -S 06_cpp20 -B 06_cpp20/build -G Ninja && cmake --build 06_cpp20/buildFull build options, presets and the helper scripts are under Building.
The numbering is roughly a learning order, but this is the recommended route — four stages, each one usable as a stopping point:
- Foundations — 01 → 16 → 02 → 14 (syntax, the build pipeline, C++11, initialization & lifetime)
- Safe modern C++ — 03 → 09 → 04 → 05 (ownership & moves, OOP done right, C++14/17, RAII everywhere)
- Generic & idiomatic C++ — 08 → 06 → 24 → 25 → 19 (templates & concepts, C++20, data structures, algorithms & ranges, coroutines)
- Professional practice — 10 → 20 → 15 → 26 → 17 → 12 → 27 → 13 → 21 → 28 → 07 → 11 (concurrency, text/IO, UB & hardening, error handling, testing, patterns, fluent APIs, architecture, advanced techniques, embedded, C++23, analysis training — then 29–32 as the modules/modernization/Qt excursions)
Projects 11, 18, 22, 23, 29, 31 and 32 are specialty labs rather than curriculum steps — static analysis, compiler conformance, modules and Qt. Skip them on a first pass and come back when the subject is the tool, not the language.
If the syntax itself is new, 01_classical_cpp/src/reading_declarations.cpp
decodes elementary declarations token by token — virtual double area() const = 0;
is taken apart piece by piece there, and the same construct is annotated in
place in inheritance_polymorphism.cpp.
| Document | Answers |
|---|---|
| TUTORIAL.md | Which C++ topic is taught where? A complete 23-chapter curriculum (core language, object/lifetime model, standard library, professional practice) mapped onto these projects — baseline C++23, C++26 strictly as a marked preview |
| USE_CASES.md | What do I reach for? Typical use cases: task → modern C++ facility (ownership, errors, callbacks, data, concurrency, compile time), problem → design pattern in its modern C++ form, system shape → architecture pattern — plus what the language absorbed and when a pattern is over-engineering |
| STRUCTURE.md | What is in which folder? Directory-by-directory map, the anatomy of a project folder, and which directories are generated |
<project>/README.md |
What does this project teach, file by file? Every project has one — click any folder above |
| docs/ | Doxygen setup: cd docs && doxygen Doxyfile builds a browsable HTML view of the annotated sources (comments preserved, directory and class diagrams included) |
| axivion/README_AXIVION.md | Static-analysis integration, rule sets, and the findings write-ups |
| CHANGELOG.md | What changed and when? Including the corrections — several published claims here were wrong until they were measured |
| CONTRIBUTING.md | How do I add or fix something? The rules a sample must satisfy, and what to run before opening a pull request |
The full catalogue, in numeric order. Every project folder has its own README explaining it file by file — and see Learning paths above if you would rather be told where to start than pick from a list.
| Project | Standard | Content |
|---|---|---|
01_classical_cpp |
C++98-era¹ | reading declarations (what const means in each position, what = 0 means, references vs pointers, member initializer lists — start here if the syntax is new), RAII, rule of three (copy ctor/assignment), copy-and-swap, inheritance, virtual dtors, multiple inheritance + diamond/virtual bases, operator overloading, friends, exceptions |
02_cpp11_language |
C++11¹ | auto/decltype, nullptr, range-for, uniform init, enum class, lambdas, =default/=delete, override/final, delegating+inheriting ctors, variadic templates, constexpr, noexcept, UDLs, tuple/chrono/unordered containers |
03_cpp11_move_smart_pointers |
C++11¹ | rvalue refs, std::move, rule of five/zero, perfect forwarding, unique_ptr/shared_ptr/weak_ptr, custom deleters |
04_cpp14 |
C++14 | generic lambdas, init-capture, return type deduction, relaxed constexpr, variable templates, make_unique, integer_sequence, transparent comparators |
05_cpp17 |
C++17 | structured bindings, if-init, if constexpr, fold expressions, CTAD, optional/variant/any/string_view, filesystem, from_chars, guaranteed copy elision |
06_cpp20 |
C++20 | concepts, ranges + projections, coroutines (hand-written generator), operator<=>, designated init, span, format, consteval/constinit, bit_cast, endianness (std::endian + guarded C++23 byteswap), source_location |
07_cpp23 |
C++23² | deducing this, if consteval, expected, print, multidim operator[], static operator(), auto(x), zip, ranges::to, move_only_function, flat_map/mdspan |
08_templates |
C++20 | specialization (full/partial), NTTPs, template template params, SFINAE/enable_if, detection idiom, tag dispatch, CRTP, mixins, deduction guides, policy-based design |
09_oop_principles |
C++17 | four OOP pillars, SOLID (one demo each), NVI, PIMPL (real header/impl split), composition over inheritance |
10_concurrency |
C++20² | thread/jthread, mutex + RAII locks, condition_variable, async/future/promise/packaged_task, atomics, call_once, latch/barrier, thread-safe interface design (check-then-act race, monitor object, deadlock avoidance via scoped_lock) |
11_axivion_challenge |
C++23² | frontend torture test for Axivion cafeCC — see below |
12_design_patterns |
C++20 | GoF modernised: Singleton, Factory Method, Abstract Factory, Builder, Prototype, Adapter, Decorator, Composite, Facade, Strategy, Observer, Command, State, Visitor |
13_architecture_patterns |
C++20 | layered, hexagonal (ports & adapters), pipes & filters, event bus, SOA (contracts + service registry), SaaS (multi-tenancy, plans, quotas, metering) |
14_initialization_lifetime |
C++20 | the five initialization forms, aggregates + designated init, most vexing parse, storage durations, temporaries + lifetime extension, static-init-order fiasco + constinit, padding/alignment, value categories, the four casts |
15_correctness_ub |
C++20 | dangerous corners with broken AND fixed variants: dangling pointers, use-after-free, double delete, iterator invalidation, dangling string_view, signed overflow, unsigned wraparound, strict aliasing, null deref — broken code runs only with --run-broken under sanitizers |
16_translation_and_linkage |
C++20 | translation units, compilation vs. linking, static library and header-only INTERFACE library (incl. PRIVATE/PUBLIC/INTERFACE semantics), ODR, internal/external linkage, inline functions/variables, ADL, preprocessor, extern "C", linker-error anatomy, modules status |
17_testing |
C++20 | unit tests (self-contained mini-framework mapped to GoogleTest/Catch2/Qt Test), CTest integration, test doubles + dependency injection, parameterized/table-driven tests (mapped to TEST_P / GENERATE / Qt data-driven), exercise with tests |
18_misra_deviations |
C++17² | deliberate MISRA catalogue: compilable, runtime-safe code violating 164 of 179 MISRA C++:2023 rules (92 %) + 56 CWE rules — 1579 findings, every violation annotated with rule number and title; training material for static-analysis triage |
19_coroutines_async |
C++20² | co_await: the awaiter protocol traced live, Task<T> with continuations and symmetric transfer, a minimal scheduler, cooperative interleaving without threads, task composition, exceptions across co_await, cancellation; the std::generator demo builds as C++23 when the compiler has it and degrades honestly otherwise |
20_text_and_io |
C++20 | stream state, sticky manipulators, string streams, std::regex, char8_t/UTF-16/wchar_t + the library's Unicode limits, <random> done properly, the C++20 chrono calendar, complex, bitset, midpoint/lerp, floating-point reality |
21_advanced_techniques |
C++20 | type erasure (hidden hierarchy + manual vtable), custom allocators, std::pmr arenas, placement new + explicit destruction, std::launder, alignment, std::bind vs lambdas, std::ref/reference_wrapper, mem_fn/invoke, function_ref |
22_compiler_stress |
C++20 | compiler stresser: valid code that is expensive to COMPILE, one stressor per subsystem (instantiation, constant evaluator, overload resolution/subsumption, mangling/back end), scaled by -DTRAINING_STRESS_LEVEL=1..5 with measure.sh reporting time and peak memory per level |
23_frontend_conformance |
C++23² | conformance challenge: 67 probes + a generated 657-case stress corpus (single features, compositions, feature-guarded C++26, plus two hunt families that drill around confirmed defects), one per translation unit, plus probe.sh producing a PASS/FAIL/SKIP/CRASH matrix across g++/clang++/cafeCC/cl — findings include cafeCC exiting 0 on failed compilations (writes IR anyway — a CI hazard that had faked earlier PASS verdicts), two distinct cafeCC crash defects (one on valid C++20: mangling sizeof... of an init-capture pack — array bound, template argument or nested re-capture — under static_assert → lower_name.c:7380, boundary measured probe by probe), GCC ICEs on the uncaptured-array family, alias CTAD (P1814) broken in both Clang 18 and cafeCC, captured structured bindings in constexpr (P1091) rejected by Clang and cafeCC, GCC 13 rejecting = default constexpr virtual dtors, and — from the stress corpus — a silent miscompare of defaulted <=> over a member array (its boundary now fully mapped: ordering is equal for any array pair, equality correct), folds and closures rejected in template-argument positions, GCC's unimplemented noexcept_expr/requires_expr mangling (cafeCC gets it right), a constexpr recursion limit of 256 (vs. the recommended 512), and lambdas inside constraints evaluating wrongly |
24_data_structures |
C++20 | data structures by hand, then from the library: singly and doubly linked lists (sentinel ring, O(1) splice, the recursive destructor trap), a binary search tree with all four traversals and its degenerate case, separate chaining vs. linear probing, a binary heap in a vector, graphs (BFS/DFS, topological sort, Dijkstra, union-find) — each one followed by the std:: container that replaces it, plus the complexity and invalidation table |
25_algorithms_and_ranges |
C++20 | the algorithms: the <algorithm>/<numeric> catalogue by intent (search, binary search, sort/stable_sort, partition, rotate, erase-remove, accumulate's init-type trap, set operations), strict weak ordering and how to test a comparator, projections and what they cost, the iterator categories, insert/stream/reverse/move iterators, the C++20 sentinel, and views: laziness measured, filter_view caching, std::ranges::dangling |
26_error_handling |
C++23² | error handling done properly: choosing the channel (optional / expected+monadic ops / error codes / exceptions with context), the three exception-safety guarantees with a verified-torn assignment and its copy-and-swap fix, why moves must be noexcept (vector growth measured: 28 copies vs 28 moves), scope guards with commit/rollback |
27_fluent_api |
C++20 | fluent APIs: method chaining and why the &&-qualified overload matters (0 copies vs 2, measured), typestate builders where build() does not exist until the required fields are set (proved with concepts), an expression-template predicate DSL that is fully constexpr, operator| pipelines written from scratch with stage composition, fluent wrappers over types you do not own — and when fluency is the wrong choice |
28_embedded_cpp |
C++20 | modern C++ on a microcontroller — which constructions and why: consteval tables, strong types, templates instead of virtual (vptr cost measured), if constexpr board variants; life without the heap (span, fixed-capacity vector, pmr on a std::array); what volatile does and does not promise, typed MMIO registers, <bit>/bit_cast; ISR safety (is_always_lock_free, release/acquire, a lock-free SPSC queue, RAII interrupt lock); -fno-exceptions/-fno-rtti, constinit, and the free/budgeted/excluded cost table |
29_modules |
C++20 | modules: every unit kind once — primary interface (export module, module linkage), a partition (:stats), an implementation unit whose edits never recompile importers — with honest toolchain gating (CMake ≥ 3.28 + Ninja + Clang 16+/MSVC 19.34+/GCC 14+; GCC 13 prints SKIPPED — only -fmodules-ts) |
30_modernization |
C++20 | the legacy→modern playbook: four refactoring steps with legacy and modern side by side producing identical printed results — ownership (new[]/rule-of-three → rule of zero), interfaces (out-params/void* callbacks → optional/lambdas), types (macros/C arrays → constexpr/std::array, incl. the MAX(v++,0) double-evaluation trap live), loops (index arithmetic → named algorithms) — each step naming the clang-tidy/analysis checks that find the legacy form |
31_qt_bridge |
C++20 | modern C++ meets Qt (needs Qt 6 Core, honest SKIP without): copy-on-write vs move semantics proven with constData() identity incl. the accidental-detach trap, UTF-16 vs UTF-8 sizes of one word, QObject parent-tree deletions counted by destructors vs unique_ptr, compile-checked connects, and moc's template limit with the signal-base idiom |
32_qt_samples |
C++23–26² | the Qt 6 technology tour (needs Qt ≥ 6.8, verified with Qt 6.11.1; honest SKIP per missing module): seven self-contained apps — Qt Widgets 2D scope painted in paintEvent over one shared QAbstractTableModel, Qt Quick telemetry board (QML_ELEMENT list model, C++ bindable QProperty, Shapes, states/transitions), Qt Quick 3D with the mesh generated in C++ (QQuick3DGeometry, PBR), live-scrolling Qt Charts, Qt Concurrent future pipelines (mappedReduced, QPromise from std::jthread, whenAll), an in-process TCP echo built on QDataStream read transactions, and Qt SQL chained through std::expected::and_then — built as C++26 (cxx_std_26) where the toolchain has it, incl. feature-guarded = delete("reason") and std::add_sat |
¹ built as C++14 because that is the lowest language mode MSVC supports.
² C++23/20 library features are guarded with feature-test macros
(__cpp_... / __cpp_lib_... from <version>) and degrade to an
informative message — the projects compile on toolchains with partial
support (e.g. GCC 13 lacks deducing this and std::print).
Verified toolchains: GCC 13 and Clang 18, Debug and Release, both
warning-free with -Wall -Wextra -Wpedantic — enforced in CI with
-DTRAINING_WERROR=ON, which turns warnings into errors. Written to compile
with MSVC 19.35+ / Visual Studio 2022 17.5+ (/W4 /permissive-).
Two projects opt out of -Werror, because being diagnosable is their content:
15_correctness_ub (GCC's -Wuse-after-free fires on
deliberate use-after-free) and 23_frontend_conformance
(probes that provoke diagnostics on purpose). The opt-out is per target and
carries its reason in the CMakeLists.
All projects at once (from the repository root):
# Linux / GCC or Clang
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/05_cpp17/05_cpp17 # run any sample
# Windows / MSVC (from a VS developer prompt)
cmake -S . -B build
cmake --build build --config Debug
build\05_cpp17\Debug\05_cpp17.exeOr via CMake presets — the named configurations IDEs
pick up automatically (debug, release, werror, sanitizers, qt):
cmake --preset debug && cmake --build --preset debug
cmake --preset qt # needs: export QT_KIT=$HOME/Qt/6.11.1/gcc_64
ctest --preset debugEvery project is also a standalone CMake project (used by the per-project Axivion analysis):
cmake -S 06_cpp20 -B 06_cpp20/build -G Ninja
cmake --build 06_cpp20/buildThe C++23 projects (07, 11, 26) are skipped automatically if the
compiler has no C++23 mode.
Or use the scripts:
./build_no_axivion.sh # configure + build + run all samples + ctest,
# no Axivion Suite required at all
./build_all.sh # the same, plus an Axivion analysis per projectbuild_no_axivion.sh is configurable through the environment:
BUILD_DIR, BUILD_TYPE=Release, CXX=clang++, SANITIZERS=ON,
SKIP_RUN=1. Verified with GCC 13 Debug and Clang 18 Release.
Run the tests with CTest — 30 in a default build: a smoke test per sample
(every project must build and run with exit code 0) plus project 17's unit
tests. A Qt-enabled build (--preset qt) registers the 32_qt_samples apps
as well, for 37:
ctest --test-dir build
ctest --test-dir build -R smoke # only the smoke testsCompiler stress measurement (project 22 — default level 1 keeps normal builds fast; higher levels are the experiment):
cmake -S . -B build -DTRAINING_STRESS_LEVEL=3 # raise the load
./22_compiler_stress/measure.sh 1 4 # time + memory per level
cmake --build build --target 22_stress_report # GCC per-phase timingSanitizer build (used by project 15's --run-broken mode):
cmake -S . -B build-asan -G Ninja -DTRAINING_SANITIZERS=ON
cmake --build build-asan
./build-asan/15_correctness_ub/15_correctness_ub --run-brokenC++20 modules are intentionally not included: compiler/CMake support
is still too uneven across GCC/MSVC to make a reliable training sample;
revisit once import std; works out of the box on both.
Verified against Axivion Suite 7.12.3: all configurations pass
axivion_ci --validate, and cafeCC builds run end-to-end.
- Each project has a complete config directory
<project>/axivion/(axivion_config.json+ci_config.json+ rule layer + generated GCC compiler profile +start_analysis.sh), created byaxivion/make_axivion_configs.sh. - Rule sets (
rule_config_cpp.json):MisraC++2023+CWE, withStaticSemanticAnalysisenabled (default parameters) for the global cross-TU rule variants. Note: MISRA C++'s newest standard is 2023; the "2025" release is MISRA C:2025, which does not apply to C++ code. - The installation integrity check is switched off per project via
Project-GlobalOptions/advanced/check_installation_integrity: false. - Run one analysis:
./05_cpp17/axivion/start_analysis.sh(append--only_buildfor a cafeCC-only check,--validatefor a config check). Run all: phase 3 of./build_all.sh. axivion/README_AXIVION.mddocuments the flow, the configuration choices, and a real frontend finding that project 11 caught (cafeCC 7.12.3 rejects libstdc++ 13's<type_traits>in C++23 mode).
23_frontend_conformance is the systematic version: one probe per feature,
so a rejection is attributable rather than buried. ./23_frontend_conformance/probe.sh -s 23
prints the matrix. Measured here: cafeCC 7.12.3 accepts 26 of 28 feature
probes at C++23 (including deducing this, which GCC 13 lacks); alias-template
CTAD (P1814) is broken in both Clang 18 and cafeCC; and the sharpest
finding is in the driver: cafeCC diagnoses ordinary compile errors, then
exits 0 and writes an IR object anyway — probe.sh therefore judges cafeCC
by its diagnostics, not its exit code.
11_axivion_challenge is deliberately dense, standard-conforming
C++20/23: string NTTPs, lambdas in unevaluated contexts as unique tag
types, compile-time std::sort, constexpr virtual dispatch, UDL
character packs with digit separators, deducing this, and a coroutine
generator that models std::ranges::input_range feeding a views pipeline.
GCC and Clang accept all of it warning-free — any diagnostic an analysis
frontend produces there is a tooling finding, not a code bug.
Corrections are the most valuable contribution here — especially measurements that disagree with the ones published in this repository, and compiler results from toolchains other than GCC 13 / Clang 18 / cafeCC 7.12.3 on Linux. See CONTRIBUTING.md for the rules every sample has to satisfy, and CODE_OF_CONDUCT.md for behaviour expectations.
Two projects contain unsafe or non-conforming code on purpose — 15_correctness_ub and 18_misra_deviations; SECURITY.md explains what is worth reporting and what is working as intended.
MIT — use the samples in your own code, teaching or otherwise.