Modern Features
By the end of this lesson you'll write modern, idiomatic C++: let the compiler deduce types with auto , loop cleanly with range-based for , pass behaviour around with lambdas, model "maybe a value" with std::optional , unpack data with structured bindings, and compute at compile time with constexpr .
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of "old" C++ as filling in a paper form where you must hand-write the same details in every box. Modern C++ is the smart online form: auto auto-fills the type for you, range-based for reads every row without you tracking line numbers, and std::optional is a field that's clearly marked "may be left blank" instead of you writing -1 and hoping the next reader knows it means "empty". The language does the bookkeeping so you can focus on the meaning.
You select a standard with a compiler flag, e.g. -std=c++17 or -std=c++20 . Our runner uses a modern standard, so every example below runs as written.
1. auto , Range-Based for , nullptr & Brace Init
auto tells the compiler "work out the type from the value" — it's still fully static, just less typing. Range-based for walks every element without you managing an index; reach for const auto& by default so you read each element with no copying. nullptr is the type-safe "no pointer" (never use 0 or NULL any more), and brace initialisation with {' '} works for everything and blocks silent narrowing. Read the worked example, run it, then you'll write your own.
Your turn. The program below averages some temperatures — fill in the two blanks marked ___ using the hints, then run it.
2. Lambdas, Structured Bindings & std::optional
A lambda is a function you can write inline and store in a variable: [capture](args){' body '} . The capture list decides which outside variables it can see — [x] copies x (by value), [&x] shares it (by reference). A structured binding , auto [a, b] = pair; , unpacks a pair, tuple, or struct into named pieces in one line. And std::optional<T> models "a T that might be missing" — check it with .has_value() and read it safely with .value_or(fallback) . The if (init; cond) form (C++17) lets you declare and test in a single, tightly-scoped line.
Now you try. Split a pair with a structured binding, then guard an optional before reading it. Fill in the two blanks:
3. constexpr , switch with Initialiser & a C++20 Peek
constexpr marks something the compiler can compute before the program runs — so it costs nothing at run time and can do things ordinary values can't, like sizing an array. The switch (init; value) form mirrors the if initialiser, keeping a helper variable scoped to just that block. Finally, a brief look at C++20 : concepts name a requirement a template type must meet (clearer errors than the old SFINAE tricks), and ranges let you pipe algorithms together with | .
No blanks this time — just a brief and an outline to keep you on track. Combine everything: a vector of pairs, a function returning std::optional , a range-based for , and structured bindings. Build it, run it, and check your output against the example in the comments.
Practice quiz
What is true about auto x = 5; in C++?
- x can later hold text, like in JavaScript
- x is deduced as int at compile time and stays int
- auto defers the type to run time
- auto makes x a reference to 5
Answer: x is deduced as int at compile time and stays int. auto is fully static — the compiler deduces one fixed type (here int) at compile time; it never changes.
In a range-based for loop, why is const auto& the safe default?
- It copies each element for safety
- It reads each element with no copy and can't mutate the source
- It is required for the loop to compile
- It converts every element to a string
Answer: It reads each element with no copy and can't mutate the source. const auto& binds a read-only reference, so there is no copying and you cannot accidentally change the container.
A lambda captures a local by reference with [&] and is then used after that local goes out of scope. What happens?
- It safely reads a copy of the value
- A compile error
- Undefined behaviour from a dangling reference
- The lambda recreates the variable
Answer: Undefined behaviour from a dangling reference. [&] captures by reference; using the lambda after the referenced variable dies is a dangling reference — undefined behaviour. Capture by value if the lambda outlives the variable.
What does std::optional<T> model?
- A T that might be missing, checkable with has_value()
- A pointer that is always non-null
- A T that is computed lazily on first use
- A thread-safe wrapper around T
Answer: A T that might be missing, checkable with has_value(). optional<T> represents 'maybe a value' — far clearer than a magic -1; check with has_value() or read safely with value_or(fallback).
What does calling .value() on an empty std::optional do?
- Returns 0
- Returns a default-constructed T
- Throws std::bad_optional_access
- Returns nullptr
Answer: Throws std::bad_optional_access. Reading an empty optional with .value() throws bad_optional_access; guard with has_value() or use value_or().
What does a structured binding like auto [name, level] = player; do?
- Creates two pointers into player
- Unpacks a pair/tuple/struct into named pieces in one line
- Sorts the members of player
- Only works on std::array
Answer: Unpacks a pair/tuple/struct into named pieces in one line. Structured bindings (C++17) split a pair, tuple, or struct into named variables in a single declaration.
How does constexpr differ from const?
- They are identical
- const must be compile-time; constexpr may be run-time
- constexpr must be computable at compile time; const may be computed at run time
- constexpr only applies to pointers
Answer: constexpr must be computable at compile time; const may be computed at run time. const means 'not changeable after set' (possibly run-time); constexpr is stronger — the value must be known at compile time, so it can size arrays.
Given constexpr int square(int x){ return x*x; }, what is the value of square(4) used to size an array?
- 8
- 16
- 4
- It cannot size an array
Answer: 16.