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.