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

How to actually work through this

  1. Don't read it linearly. Take Stage 1 into your fingers first — rewrite an old-style loop or sort you already wrote.
  2. Compile with -std=c++20 -Wall(or c++23). Half of "modern C++" is just not writing C++03.
  3. One feature per practice problem. Our 36 problems are deliberately C++-core; rewrite one using std::vector + range-for + auto as an exercise.
  4. 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.