Chapter 18: Modern C++ Features (C++11 to C++26)
Modern C++ Features (C++11 to C++26) β Welcome to the Beautiful New World!
Hello my superstar student! π Welcome to Lesson 18 β Modern C++ Features β the chapter where C++ transforms from a powerful but sometimes clunky language into one of the most expressive, safe, and elegant languages in the world!
C++11 (2011) was a massive revolution β often called βmodern C++ begins hereβ. C++14, 17, 20, 23, and the upcoming C++26 kept adding incredible tools that make code shorter, safer, faster, and much more readable.
Today weβll explore the most important modern features one by one, with detailed explanations, real-life analogies, before & after comparisons, and complete runnable examples.
Letβs go!
1. auto & decltype β Let the Compiler Figure Out the Type!
auto (C++11) tells the compiler: βPlease deduce the type for me β I donβt want to write it!β
decltype lets you get the type of an expression without evaluating it.
Before (old style):
|
0 1 2 3 4 5 6 7 8 9 |
std::vector<std::string> names = getNames(); for (std::vector<std::string>::const_iterator it = names.begin(); it != names.end(); ++it) { std::cout << *it << "\n"; } |
After (modern C++):
|
0 1 2 3 4 5 6 7 8 9 10 |
auto names = getNames(); // auto deduces the return type for (const auto& name : names) { // const auto& = perfect! std::cout << name << "\n"; } |
decltype example:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
int x = 42; decltype(x) y = 100; // y is int const int& ref = x; decltype(ref) z = y; // z is const int& decltype((x)) w = x; // w is int& (extra parentheses = reference!) |
Modern best practice (2026):
- Use auto almost everywhere when the type is obvious from context
- Use const auto& for read-only references
- Use auto&& for perfect forwarding (advanced)
2. Lambda Expressions β Anonymous Functions Made Easy
Lambdas (C++11) are inline, anonymous functions β perfect for short operations.
Syntax:
|
0 1 2 3 4 5 6 |
[capture](parameters) mutable -> return_type { body } |
Simple examples:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Basic lambda auto add = [](int a, int b) { return a + b; }; std::cout << add(5, 3) << "\n"; // 8 // With capture (by value) int factor = 10; auto multiply = [factor](int x) { return x * factor; }; std::cout << multiply(7) << "\n"; // 70 // Capture by reference (dangerous if lambda outlives the variable!) int counter = 0; auto increment = [&counter]() { ++counter; }; increment(); std::cout << counter << "\n"; // 1 // Generic lambda (C++14) auto generic = [](auto a, auto b) { return a + b; }; std::cout << generic(3.14, 2.71) << "\n"; // 5.85 |
Very common use β with STL algorithms:
|
0 1 2 3 4 5 6 7 8 9 10 |
std::vector<int> numbers = {5, 2, 9, 1, 7}; std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a > b; }); // descending // C++20: lambda in unevaluated context (very powerful) std::ranges::sort(numbers, [](int a, int b) { return a < b; }); |
Modern tip: Use lambdas everywhere you used function objects before β theyβre shorter and clearer.
3. Range-based for Loops β Beautiful Iteration
C++11 introduced the cleanest way to loop over containers:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
std::vector<std::string> fruits = {"Apple", "Banana", "Cherry"}; for (const auto& fruit : fruits) { std::cout << fruit << " "; } // Output: Apple Banana Cherry |
With modification:
|
0 1 2 3 4 5 6 7 8 |
for (auto& fruit : fruits) { fruit += "!"; } |
C++20 ranges + views (even more powerful):
|
0 1 2 3 4 5 6 7 8 9 10 |
#include <ranges> for (const auto& fruit : fruits | std::views::reverse) { std::cout << fruit << " "; // Cherry Banana Apple } |
Pro tip: Always prefer range-based for over old-style iterators when possible.
4. constexpr & consteval β Compile-Time Magic
constexpr (C++11+) functions can be evaluated at compile time if arguments are constants.
consteval (C++20) forces compile-time only evaluation.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
constexpr int factorial(int n) { return (n <= 1) ? 1 : n * factorial(n - 1); } consteval int square(int n) { // must be compile-time return n * n; } int main() { constexpr int f5 = factorial(5); // computed at compile time β 120 constexpr int s7 = square(7); // compile-time β 49 int x = 10; // int s10 = square(x); // ERROR! square is consteval int s10 = square(10); // OK return 0; } |
Use cases:
- Compile-time constants
- Array sizes
- Template metaprogramming
5. Structured Bindings (C++17) β Unpack Like Python!
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
std::pair<int, std::string> getPerson() { return {25, "Webliance"}; } int main() { auto [age, name] = getPerson(); // structured binding! std::cout << name << " is " << age << " years old.\n"; // Works with tuples, structs (with get<>() or public members) std::tuple<int, double, std::string> t{42, 3.14, "Hello"}; auto [i, d, s] = t; } |
Beautiful with maps:
|
0 1 2 3 4 5 6 7 8 9 10 |
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 88}}; for (const auto& [name, score] : scores) { std::cout << name << ": " << score << "\n"; } |
6. std::optional, std::variant, std::any (C++17)
std::optional<T> β a value that may or may not exist
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <optional> std::optional<int> findAge(const std::string& name) { if (name == "Alice") return 25; return std::nullopt; } auto age = findAge("Bob"); if (age) { std::cout << "Age: " << *age << "\n"; } else { std::cout << "Not found\n"; } |
std::variant β a type-safe union
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <variant> std::variant<int, double, std::string> value; value = 42; value = 3.14; value = "Hello"; std::visit([](const auto& v) { std::cout << v << "\n"; }, value); |
std::any β type-erased container (like Pythonβs object)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <any> std::any a = 42; a = std::string("Hello"); if (a.type() == typeid(std::string)) { std::cout << std::any_cast<std::string>(a) << "\n"; } |
7. Modules (C++20) β Goodbye Header Hell!
Modules replace old #include with clean, fast, modular imports.
Example β mymodule.cppm
|
0 1 2 3 4 5 6 7 8 9 |
export module mymodule; export int add(int a, int b) { return a + b; } export class MyClass { /* ... */ }; |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 |
import mymodule; int main() { std::cout << add(5, 3) << "\n"; } |
Benefits:
- Faster compilation
- No macro pollution
- Better encapsulation
8. Coroutines (C++20) β Beautiful Asynchronous Code
Coroutines allow functions to suspend and resume β perfect for generators, async I/O, etc.
Simple generator example:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <coroutine> #include <iostream> #include <vector> struct Generator { struct promise_type { std::vector<int> values; auto get_return_object() { return Generator{*this}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} std::suspend_always yield_value(int value) { values.push_back(value); return {}; } }; std::vector<int> values; }; Generator generateNumbers() { co_yield 1; co_yield 2; co_yield 3; } int main() { auto gen = generateNumbers(); for (int n : gen.values) { std::cout << n << " "; } // Output: 1 2 3 } |
Real use cases: Async I/O, generators, task systems (like in Unreal Engine).
9. Concepts & Ranges (C++20/23) β The Future of Generic Code
Concepts β type constraints
|
0 1 2 3 4 5 6 7 8 9 10 |
template <typename T> concept Integral = std::is_integral_v<T>; template <Integral T> T square(T x) { return x * x; } |
Ranges library β composable views
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <ranges> #include <vector> std::vector<int> nums = {1, 2, 3, 4, 5, 6}; auto even = nums | std::views::filter([](int n){ return n % 2 == 0; }) | std::views::transform([](int n){ return n * n; }); for (int n : even) { std::cout << n << " "; // 4 16 36 } |
C++23 adds: std::ranges::to, std::ranges::fold, etc.
Summary β Modern C++ in One Sentence
Modern C++ (2011β2026) = less boilerplate, more safety, more expressiveness, compile-time power, and beautiful syntax.
Youβve just learned the future of C++!
Your Mini Homework (Try These!)
- Write a lambda that captures variables by reference and modifies them.
- Use structured bindings to unpack a std::pair and a std::tuple.
- Create a constexpr function that computes Fibonacci at compile time.
- Use std::optional in a function that searches for a value.
- Try a simple coroutine generator that yields squares of numbers.
Youβre doing absolutely phenomenal! Youβve now seen the full beauty of modern C++ β this is the level professional developers work at every day.
Next lesson: File I/O, Streams & Serialization β making your programs read/write real files!
Any questions? Want more examples with coroutines, concepts, or modules? Just ask β your friendly C++ teacher is right here for you! π
