Roadmap · continuously updated
Modern C++ roadmap: C++11 → C++23
Most beginner material teaches C++ as it looked in 2003. Real code in 2026 doesn't. This is the order to pick up modern features — each entry shows the old way, the new way, and why it matters.
Note: our in-browser playground covers the teaching core only (no std::string, vector, or C++11+ features). The snippets below are for your desktop compiler — see how to compile C++.
Stage 1 — C++11/14: the baseline (learn these first)
These are not "advanced". Any compiler from the last decade supports them, and every professional codebase uses them. If a tutorial doesn't teach these, it is teaching you a museum piece.
1. auto — let the compiler name the type
// old
std::vector<std::string>::iterator it = names.begin();
// modern
auto it = names.begin(); // type deduced, still statically typed
Why: types get long and noisy; auto keeps the type correct even when you change the container. It is still static typing — nothing happens at runtime.
2. Range-based for — iterate without index gymnastics
// old
for (size_t i = 0; i < v.size(); ++i) sum += v[i];
// modern
for (int x : v) sum += x;
// read-only, no copy — the idiom you'll see everywhere
for (const auto& name : names) cout << name << '\n';
Why: no off-by-one, no index variable. const auto& is the default you should reach for: no copy, no accidental modification.
3. Smart pointers — stop calling new/delete by hand
// old — leak-prone, exception-unsafe
Widget* w = new Widget();
... // if this throws, w leaks
delete w;
// modern — ownership is explicit in the type
auto w = std::make_unique<Widget>(); // sole owner, freed automatically
auto s = std::make_shared<Widget>(); // shared ownership (use sparingly)
Why: RAII in one line. unique_ptr should be your default for heap objects; shared_ptr only when ownership is genuinely shared.
4. Lambdas — pass behavior, not just data
// sort by a custom rule, without writing a named function
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) { return a.age < b.age; });
// capture local state by value ([x]) or reference ([&x])
int threshold = 30;
auto over = [threshold](int age) { return age > threshold; };
Why: lambdas made "pass a small function" idiomatic and unlocked modern algorithms. You'll meet them in every codebase written after 2011.
5. nullptr, enum class, constexpr
char* p = nullptr; // not 0, not NULL — type-safe
enum class Color { Red, Green }; // scoped: Color::Red, no implicit int
constexpr int kMax = 100; // computed at compile time
Stage 2 — C++17: quality of life
6. Structured bindings
std::map<std::string, int> ages;
for (const auto& [name, age] : ages) { // unpack pairs directly
cout << name << " = " << age << '\n';
}
7. std::optional — "maybe a value" in the type
std::optional<int> parse(std::string_view s); // may fail
if (auto n = parse(input)) { use(*n); } // no sentinel -1, no bool out-param
Why: replaces magic return values and output parameters. The failure mode is now visible in the signature.
8. std::string_view
void log(std::string_view msg); // no copy, works with literals and strings
Stage 3 — C++20: the big one (where "modern" really starts)
9. Concepts — constraints the compiler understands
template <typename T>
requires std::integral<T>
T twice(T x) { return x * 2; }
// error messages become human: "T doesn't satisfy integral"
// instead of pages of template instantiation noise
Why: this is the headline C++20 feature. It turns template errors from unreadable into checkable requirements.
10. Ranges — pipelines instead of loop bodies
#include <ranges>
auto result = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::views::take(5); // lazy: nothing runs until iterated
Why: reads like the problem statement. Lazy evaluation means no temporary vectors. This is the biggest style shift since range-for.
11. Modules — replace the #include text-paste
// math.cppm
export module math;
export int add(int a, int b) { return a + b; }
// main.cpp
import math; // no header guards, no re-parsing the world
Why: faster builds and real module boundaries. Toolchain support is still uneven in 2026 — know it, don't depend on it everywhere yet.
12. Coroutines, std::format, span, consteval
std::cout << std::format("{} + {} = {}", a, b, a + b); // type-safe printf
void process(std::span<int> data); // array view, no decay
consteval int square(int x) { return x * x; } // must run at compile time
Stage 4 — C++23 and beyond: what's landing now
std::expected<T, E>— error handling without exceptions or optional-blindness: the return type carries the failure reason.std::print/std::println—formatfinally wired into a one-liner.std::mdspan— multi-dimensional views over flat memory (the numeric-computing crowd has wanted this for years).- Deducing
this— explicit object parameters, cleaner CRTP and mixins. - What to watch: reflection (C++26 target), contracts, and pattern matching — the three that would change how everyday code reads.
How to actually work through this
- Don't read it linearly. Take Stage 1 into your fingers first — rewrite an old-style loop or sort you already wrote.
- Compile with
-std=c++20 -Wall(orc++23). Half of "modern C++" is just not writing C++03. - One feature per practice problem. Our 36 problems are deliberately C++-core; rewrite one using
std::vector+ range-for +autoas an exercise. - Read real code. Pick a modern open-source C++ project and grep for
auto&,unique_ptr,std::ranges— see how working codebases actually use them.
Related: data types, vector vs array, stack vs heap, and getting a desktop compiler.