Chapter 25 Advanced 55 min min read Updated 2026-04-10

Modern C++ Features (C++11 to C++20)

Practice Questions →

In This Chapter

What Is It?

What Are Modern C++ Features?

Modern C++ refers to the language features introduced from C++11 onward. C++11 was a landmark release that fundamentally transformed the language, followed by incremental improvements in C++14, C++17, and C++20. These features make C++ safer, more expressive, and more productive while maintaining zero-cost abstractions.

Key categories of modern features:

  • C++11 -- auto, range-based for, lambdas, nullptr, enum class, constexpr, move semantics, smart pointers, uniform initialization, using aliases
  • C++14 -- generic lambdas, return type deduction, relaxed constexpr
  • C++17 -- structured bindings, if with initializer, optional/variant/any, string_view, filesystem
  • C++20 -- concepts, ranges, the spaceship operator (<=>), coroutines
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // C++11: auto, range-based for, lambda
    vector<int> v = {5, 2, 8, 1, 9};

    // Old C++03 way
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
        cout << *it << " ";
    cout << endl;

    // Modern C++11 way
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
    for (auto x : v) cout << x << " ";
    cout << endl;

    // C++17: structured bindings
    auto [minIt, maxIt] = minmax_element(v.begin(), v.end());
    cout << "Min: " << *minIt << ", Max: " << *maxIt << endl;
    return 0;
}

When Harish writes auto [key, value] = *m.begin() or Deepika writes auto f = [&](int x) { return x * x; }, they are using modern C++ idioms that make code shorter, safer, and more readable than the equivalent C++03 code.

Why Does It Matter?

Why Are Modern C++ Features Important?

1. Drastically Reduced Boilerplate

auto eliminates verbose type declarations. Range-based for loops eliminate iterator boilerplate. Lambdas eliminate the need for separate function objects. Structured bindings eliminate .first and .second. Modern C++ lets you express intent directly instead of fighting the type system.

2. Safety Without Runtime Cost

nullptr prevents integer-pointer ambiguity bugs. enum class prevents implicit conversions. constexpr moves computation to compile-time. Smart pointers prevent memory leaks. These features add safety at zero or near-zero runtime cost -- a core C++ principle.

3. Competitive Programming Speed

In competitive programming, modern features save precious minutes: auto avoids typing long iterator types, range-based for is faster to write, lambdas enable inline comparators, and structured bindings simplify pair/tuple access.

4. Industry Standard

Every major C++ codebase (Google, Meta, Microsoft, Bloomberg, game engines) uses C++17 or later. Job interviews and production code expect fluency with modern features. Writing C++03-style code in 2026 is a red flag.

5. Foundation for Advanced Patterns

C++20 concepts enable constrained templates (no more cryptic SFINAE errors). Ranges enable composable, lazy data pipelines. These features make advanced C++ patterns accessible to a wider audience.

Detailed Explanation

Detailed Explanation

Modern C++ refers to the language features introduced from C++11 onward. C++11 was a landmark release that fundamentally transformed the language, followed by incremental improvements in C++14, C++17, and C++20. These features make C++ safer, more expressive, and more productive while maintaining zero-cost abstractions.

Key categories of modern features:

  • C++11 -- auto, range-based for, lambdas, nullptr, enum class, constexpr, move semantics, smart pointers, uniform initialization, using aliases
  • C++14 -- generic lambdas, return type deduction, relaxed constexpr
  • C++17 -- structured bindings, if with initializer, optional/variant/any, string_view, filesystem
  • C++20 -- concepts, ranges, the spaceship operator (<=>), coroutines
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // C++11: auto, range-based for, lambda
    vector<int> v = {5, 2, 8, 1, 9};

    // Old C++03 way
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
        cout << *it << " ";
    cout << endl;

    // Modern C++11 way
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
    for (auto x : v) cout << x << " ";
    cout << endl;

    // C++17: structured bindings
    auto [minIt, maxIt] = minmax_element(v.begin(), v.end());
    cout << "Min: " << *minIt << ", Max: " << *maxIt << endl;
    return 0;
}

When Harish writes auto [key, value] = *m.begin() or Deepika writes auto f = [&](int x) { return x * x; }, they are using modern C++ idioms that make code shorter, safer, and more readable than the equivalent C++03 code.

1. Drastically Reduced Boilerplate

auto eliminates verbose type declarations. Range-based for loops eliminate iterator boilerplate. Lambdas eliminate the need for separate function objects. Structured bindings eliminate .first and .second. Modern C++ lets you express intent directly instead of fighting the type system.

2. Safety Without Runtime Cost

nullptr prevents integer-pointer ambiguity bugs. enum class prevents implicit conversions. constexpr moves computation to compile-time. Smart pointers prevent memory leaks. These features add safety at zero or near-zero runtime cost -- a core C++ principle.

3. Competitive Programming Speed

In competitive programming, modern features save precious minutes: auto avoids typing long iterator types, range-based for is faster to write, lambdas enable inline comparators, and structured bindings simplify pair/tuple access.

4. Industry Standard

Every major C++ codebase (Google, Meta, Microsoft, Bloomberg, game engines) uses C++17 or later. Job interviews and production code expect fluency with modern features. Writing C++03-style code in 2026 is a red flag.

5. Foundation for Advanced Patterns

C++20 concepts enable constrained templates (no more cryptic SFINAE errors). Ranges enable composable, lazy data pipelines. These features make advanced C++ patterns accessible to a wider audience.

Code Examples

C++11: auto, Range-Based For, and Uniform Initialization
#include <iostream>
#include <vector>
#include <map>
using namespace std;

int main() {
    // auto deduces the type
    auto x = 42;          // int
    auto pi = 3.14;       // double
    auto name = "Arjun"s; // string (with s suffix)

    // Range-based for loop (replaces iterator boilerplate)
    vector<int> v = {10, 20, 30, 40, 50};
    for (auto val : v)
        cout << val << " ";
    cout << endl;

    // By reference (avoids copy)
    for (auto& val : v)
        val *= 2;
    for (const auto& val : v)
        cout << val << " ";
    cout << endl;

    // Uniform initialization with braces
    int a{42};
    vector<int> w{1, 2, 3, 4, 5};
    map<string, int> marks{{"Arjun", 85}, {"Priya", 92}};

    // auto with iterators (no more verbose type names)
    auto it = marks.find("Arjun");
    if (it != marks.end())
        cout << it->first << ": " << it->second << endl;

    return 0;
}
auto lets the compiler deduce the type, eliminating verbose declarations like map<string, int>::iterator. Range-based for replaces manual iterator loops. Uniform initialization with {} prevents narrowing conversions (e.g., int x{3.14} is a compile error). These three features alone make C++11 code dramatically cleaner than C++03.
10 20 30 40 50 20 40 60 80 100 Arjun: 85
C++11: Lambdas and Capture Modes
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // Basic lambda: [capture](params) -> return_type { body }
    auto square = [](int x) { return x * x; };
    cout << "square(5) = " << square(5) << endl;

    // Capture by value [=] and by reference [&]
    int factor = 3;
    auto multiply = [factor](int x) { return x * factor; };
    cout << "multiply(7) = " << multiply(7) << endl;

    // Capture by reference to modify
    int total = 0;
    vector<int> v = {1, 2, 3, 4, 5};
    for_each(v.begin(), v.end(), [&total](int x) { total += x; });
    cout << "Total: " << total << endl;

    // Lambda as comparator for sort
    vector<pair<string, int>> students = {
        {"Arjun", 85}, {"Priya", 92}, {"Kiran", 78}
    };
    sort(students.begin(), students.end(),
        [](const pair<string, int>& a, const pair<string, int>& b) {
            return a.second > b.second;  // sort by marks descending
        });
    for (auto& [name, marks] : students)
        cout << name << ": " << marks << endl;

    // Mutable lambda (modify captured-by-value variables)
    int counter = 0;
    auto inc = [counter]() mutable { return ++counter; };
    cout << inc() << " " << inc() << " " << inc() << endl;
    cout << "Original counter: " << counter << endl;  // unchanged

    return 0;
}
Lambdas are anonymous functions: [capture](params){ body }. Capture modes: [x] by value, [&x] by reference, [=] all by value, [&] all by reference. The mutable keyword allows modifying captured-by-value variables (the copy, not the original). Lambdas are essential for STL algorithms, custom comparators, and callbacks.
square(5) = 25 multiply(7) = 21 Total: 15 Priya: 92 Arjun: 85 Kiran: 78 1 2 3 Original counter: 0
C++11: nullptr, enum class, constexpr
#include <iostream>
using namespace std;

// nullptr replaces NULL
void process(int n) { cout << "int: " << n << endl; }
void process(int* p) { cout << "pointer: " << p << endl; }

// enum class: strongly typed, no implicit conversion
enum class Color { Red, Green, Blue };
enum class Size { Small, Medium, Large };

// constexpr: compile-time computation
constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
        int temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}

int main() {
    // nullptr: unambiguous null pointer
    int* ptr = nullptr;
    // process(NULL);    // Ambiguous! NULL is 0 (int)
    process(nullptr);    // Calls process(int*)
    process(42);         // Calls process(int)

    // enum class: scoped, no implicit int conversion
    Color c = Color::Red;
    // int x = c;        // ERROR: no implicit conversion
    int x = static_cast<int>(c);  // Explicit: 0
    cout << "Color value: " << x << endl;

    // constexpr: evaluated at compile time
    constexpr int fact5 = factorial(5);   // Computed at compile time!
    constexpr int fib10 = fibonacci(10);  // Also compile time
    cout << "5! = " << fact5 << endl;
    cout << "fib(10) = " << fib10 << endl;

    // constexpr array size
    constexpr int N = 1024;
    int arr[N];  // Valid: N is known at compile time
    cout << "Array size: " << N << endl;

    return 0;
}
nullptr is a type-safe null pointer constant that resolves overload ambiguity (unlike NULL which is just 0). enum class creates scoped enumerations with no implicit int conversion, preventing accidental comparisons between unrelated enums. constexpr functions are evaluated at compile time when given constant arguments, enabling zero-runtime-cost computations.
pointer: 0 int: 42 Color value: 0 5! = 120 fib(10) = 55 Array size: 1024
C++14: Generic Lambdas and Return Type Deduction
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    // Generic lambda (C++14): auto parameters
    auto print = [](auto x) { cout << x << " "; };
    print(42);       // int
    print(3.14);     // double
    print("hello");  // const char*
    cout << endl;

    // Generic lambda as comparator
    auto maxOf = [](auto a, auto b) { return (a > b) ? a : b; };
    cout << "max(3, 7) = " << maxOf(3, 7) << endl;
    cout << "max(3.14, 2.71) = " << maxOf(3.14, 2.71) << endl;

    // Return type deduction (C++14)
    auto add = [](auto a, auto b) { return a + b; };
    cout << "add(2, 3) = " << add(2, 3) << endl;
    cout << "add(2.5, 3.7) = " << add(2.5, 3.7) << endl;
    cout << "add strings: " << add(string("Hello"), string(" World")) << endl;

    // C++14: auto return type deduction for functions
    // auto multiply(int a, int b) { return a * b; }  // Valid in C++14

    // Generic lambda for sorting any vector
    auto sortAndPrint = [](auto& vec) {
        sort(vec.begin(), vec.end());
        for (const auto& x : vec) cout << x << " ";
        cout << endl;
    };

    vector<int> ints = {5, 3, 1, 4, 2};
    vector<string> strs = {"Kiran", "Arjun", "Priya"};
    sortAndPrint(ints);
    sortAndPrint(strs);

    return 0;
}
C++14 generic lambdas use auto for parameters, making them work like templates. The compiler generates a separate function for each type used. Return type deduction lets the compiler figure out the return type automatically. These features make lambdas as powerful as template functions but with inline convenience.
42 3.14 hello max(3, 7) = 7 max(3.14, 2.71) = 3.14 add(2, 3) = 5 add(2.5, 3.7) = 6.2 add strings: Hello World 1 2 3 4 5 Arjun Kiran Priya
C++17: Structured Bindings, If-Init, and optional
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <tuple>
using namespace std;

// Function returning optional (may or may not have a value)
optional<int> findIndex(const vector<int>& v, int target) {
    for (int i = 0; i < (int)v.size(); i++)
        if (v[i] == target) return i;
    return nullopt;  // No value
}

tuple<string, int, double> getStudent() {
    return {"Arjun", 85, 9.2};
}

int main() {
    // Structured bindings: unpack pairs, tuples, structs
    auto [name, marks, gpa] = getStudent();
    cout << name << ": " << marks << ", GPA: " << gpa << endl;

    // Structured bindings with maps
    map<string, int> scores = {{"Arjun", 85}, {"Priya", 92}, {"Kiran", 78}};
    for (auto& [student, score] : scores)
        cout << student << ": " << score << endl;

    // If with initializer (C++17)
    if (auto it = scores.find("Priya"); it != scores.end())
        cout << "Found Priya: " << it->second << endl;
    else
        cout << "Priya not found" << endl;
    // it is not accessible here (scoped to the if)

    // std::optional
    vector<int> v = {10, 20, 30, 40, 50};
    auto result = findIndex(v, 30);
    if (result.has_value())
        cout << "Found at index: " << result.value() << endl;
    else
        cout << "Not found" << endl;

    auto missing = findIndex(v, 99);
    cout << "99 found? " << missing.has_value() << endl;
    cout << "Default: " << missing.value_or(-1) << endl;

    return 0;
}
C++17 structured bindings (auto [x, y] = pair) eliminate .first/.second boilerplate. If-with-initializer limits variable scope to the if/else block. std::optional represents a value that may or may not exist, replacing sentinel values (-1, nullptr) with an explicit type.
Arjun: 85, GPA: 9.2 Arjun: 85 Kiran: 78 Priya: 92 Found Priya: 92 Found at index: 2 99 found? 0 Default: -1
C++17: string_view and using Aliases
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
using namespace std;

// string_view: non-owning reference to string data (zero-copy)
void greet(string_view name) {
    cout << "Hello, " << name << "!" << endl;
}

// Count words without copying the string
int countWords(string_view text) {
    int count = 0;
    bool inWord = false;
    for (char c : text) {
        if (c != ' ' && !inWord) { count++; inWord = true; }
        else if (c == ' ') inWord = false;
    }
    return count;
}

// using alias (C++11): cleaner than typedef
using StringVec = vector<string>;
using ScoreMap = map<string, int>;
template<typename T>
using Vec = vector<T>;  // Template alias!

int main() {
    // string_view works with string, const char*, and substrings
    string s = "Vikram Sharma";
    greet(s);              // From string
    greet("Priya");        // From literal (no string construction!)

    string_view sv = s;
    cout << "First name: " << sv.substr(0, 6) << endl;  // Zero-copy substring
    cout << "Words: " << countWords("Modern C++ is powerful") << endl;

    // using aliases in action
    StringVec names = {"Arjun", "Priya", "Kiran"};
    Vec<int> numbers = {1, 2, 3, 4, 5};  // Template alias
    for (auto& name : names) cout << name << " ";
    cout << endl;

    return 0;
}
string_view is a non-owning, read-only view into string data. It avoids copying when you just need to read a string. It works with std::string, C-string literals, and substrings. using aliases (C++11) replace typedef with cleaner syntax and support template aliases, which typedef cannot.
Hello, Vikram Sharma! Hello, Priya! First name: Vikram Words: 4 Arjun Priya Kiran
C++20: Concepts and the Spaceship Operator
#include <iostream>
#include <concepts>
#include <compare>
#include <string>
using namespace std;

// Concept: constrain template parameters
template<typename T>
concept Numeric = is_arithmetic_v<T>;

template<typename T>
concept Printable = requires(T t) {
    { cout << t } -> same_as<ostream&>;
};

// Function using concept (clear error messages!)
template<Numeric T>
T add(T a, T b) {
    return a + b;
}

// Abbreviated function template with auto + concept
void printValue(Printable auto val) {
    cout << "Value: " << val << endl;
}

// Spaceship operator <=> : three-way comparison
struct Student {
    string name;
    int marks;

    // Compiler generates ==, !=, <, >, <=, >= from <=>
    auto operator<=>(const Student&) const = default;
};

struct Score {
    int value;
    // Custom spaceship: compare by value
    auto operator<=>(const Score& other) const {
        return value <=> other.value;
    }
    bool operator==(const Score& other) const = default;
};

int main() {
    // Concepts in action
    cout << add(3, 4) << endl;       // OK: int is Numeric
    cout << add(2.5, 3.7) << endl;   // OK: double is Numeric
    // add("a", "b");                // ERROR: string is not Numeric (clear message!)

    printValue(42);
    printValue("hello");

    // Spaceship operator
    Student s1{"Arjun", 85}, s2{"Priya", 92};
    cout << (s1 < s2) << endl;   // Compares name first, then marks

    Score a{85}, b{92}, c{85};
    cout << (a < b) << " " << (a == c) << " " << (b > a) << endl;

    // Three-way comparison result
    auto result = a <=> b;
    if (result < 0) cout << "a < b" << endl;
    else if (result > 0) cout << "a > b" << endl;
    else cout << "a == b" << endl;

    return 0;
}
C++20 concepts constrain template parameters with readable requirements. Instead of cryptic SFINAE errors, you get clear messages like "T does not satisfy Numeric." The <=> (spaceship) operator generates all six comparison operators from a single declaration. = default asks the compiler to generate memberwise comparison. These features make generic C++ dramatically more approachable.
7 6.2 Value: 42 Value: hello 1 1 1 1 a < b
Old vs Modern C++: Before and After Comparison
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <memory>
#include <numeric>
using namespace std;

int main() {
    // === VARIABLE DECLARATION ===
    // Old: explicit types everywhere
    map<string, vector<int>>::iterator oldIt;
    // Modern: auto
    auto modernMap = map<string, vector<int>>{{"Arjun", {85, 90, 78}}};
    auto it = modernMap.begin();

    // === ITERATION ===
    vector<int> v = {1, 2, 3, 4, 5};
    // Old:
    for (vector<int>::iterator i = v.begin(); i != v.end(); ++i)
        cout << *i << " ";
    cout << endl;
    // Modern:
    for (auto x : v) cout << x << " ";
    cout << endl;

    // === NULL POINTER ===
    // Old: NULL (which is 0, an int)
    int* oldPtr = NULL;
    // Modern: nullptr (type-safe)
    int* newPtr = nullptr;

    // === FUNCTION OBJECTS ===
    // Old: write a whole struct
    struct OldCompare {
        bool operator()(int a, int b) { return a > b; }
    };
    sort(v.begin(), v.end(), OldCompare());
    // Modern: lambda
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

    // === MEMORY MANAGEMENT ===
    // Old: raw new/delete
    int* oldArr = new int[5];
    delete[] oldArr;
    // Modern: smart pointer
    auto smartArr = make_unique<int[]>(5);

    // === TYPE ALIASES ===
    // Old: typedef
    typedef vector<pair<string, int>> OldType;
    // Modern: using
    using ModernType = vector<pair<string, int>>;

    cout << "Modern C++ is cleaner and safer!" << endl;
    return 0;
}
This program shows the same operations in old C++03 style and modern C++11/17 style. Every modern replacement is shorter, safer, or both. The transformation is dramatic: auto replaces verbose types, range-based for replaces iterator loops, nullptr replaces NULL, lambdas replace function objects, smart pointers replace raw new/delete, and using replaces typedef.
1 2 3 4 5 1 2 3 4 5 Modern C++ is cleaner and safer!

Common Mistakes

Using auto When the Type Is Unclear or Surprising

auto x = 3.14f;  // float, not double!
auto y = {1, 2, 3};  // initializer_list<int>, not vector!
auto z = "hello";  // const char*, not string!
auto deduces the exact type of the initializer, which may not be what you expect. 3.14f is float, not double. A braced list is initializer_list, not vector. A string literal is const char*, not std::string.
double x = 3.14;           // Explicit when type matters
vector<int> y = {1, 2, 3}; // Explicit vector
string z = "hello";        // Explicit string
// Or use auto with the right initializer:
auto x2 = 3.14;            // double (no f suffix)
auto z2 = "hello"s;        // string (with s suffix)
Use auto when the type is obvious from context (iterators, lambda results, make_unique results). Use explicit types when the deduced type might be surprising or when you want a specific type.

Capturing by Reference in a Lambda That Outlives the Scope

auto createCounter() {
    int count = 0;
    return [&count]() { return ++count; };  // Dangling reference!
}
auto counter = createCounter();
cout << counter() << endl;  // Undefined behavior!
The lambda captures count by reference, but count is destroyed when createCounter returns. The lambda now holds a dangling reference. Calling it is undefined behavior.
auto createCounter() {
    int count = 0;
    return [count]() mutable { return ++count; };  // Capture by value
}
auto counter = createCounter();
cout << counter() << endl;  // Safe: 1
Capture by value ([count]) when the lambda outlives the captured variable's scope. Use mutable to allow modification of the captured copy. Only capture by reference when you are certain the referenced variable will outlive the lambda.

Forgetting That constexpr Functions Must Be Evaluable at Compile Time

constexpr int readInput() {
    int x;
    cin >> x;  // ERROR: cin is not constexpr
    return x;
}
constexpr functions can only use operations that are evaluable at compile time. I/O operations, dynamic memory allocation (in C++11), and non-constexpr function calls are not allowed.
constexpr int square(int x) {
    return x * x;  // Pure computation, no I/O
}

constexpr int result = square(5);  // Evaluated at compile time
int runtime = square(n);  // Can also be called at runtime
A constexpr function must be evaluable at compile time when given constant arguments. It can still be called at runtime with non-constant arguments. Keep constexpr functions pure: no I/O, no global state modification.

Using string_view After the Source String Is Destroyed

string_view getDangling() {
    string s = "temporary";
    return string_view(s);  // s destroyed, view is dangling!
}
string_view sv = getDangling();
cout << sv << endl;  // Undefined behavior!
string_view is a non-owning reference. If the underlying string is destroyed, the string_view becomes a dangling pointer. Accessing it is undefined behavior.
// Option 1: Return string, not string_view
string getSafe() {
    string s = "temporary";
    return s;  // Move semantics, efficient
}

// Option 2: Use string_view only for function parameters
void process(string_view sv) {
    cout << sv << endl;  // Safe: caller owns the string
}
string_view should primarily be used for function parameters and local references to existing strings. Never return a string_view that refers to a local variable. If you need to return string data, return std::string.

Structured Bindings with Wrong Number of Variables

pair<int, string> p = {1, "hello"};
auto [a, b, c] = p;  // ERROR: pair has 2 elements, not 3
Structured bindings require the exact number of variables to match the number of elements in the pair, tuple, or struct. Mismatching counts is a compile error.
pair<int, string> p = {1, "hello"};
auto [a, b] = p;  // Correct: 2 variables for pair
cout << a << " " << b << endl;

tuple<int, string, double> t = {1, "hello", 3.14};
auto [x, y, z] = t;  // Correct: 3 variables for 3-element tuple
The number of variables in a structured binding must exactly match the number of members. For pairs, use 2 variables. For tuples, use the exact tuple size. For structs, match the number of public non-static data members.

Summary

  • C++11 auto deduces types from initializers, eliminating verbose type declarations. Use it for iterators, lambda results, and make_unique/make_shared results. Avoid it when the deduced type might be surprising.
  • Range-based for loops (for (auto x : container)) replace manual iterator loops. Use auto& for references (modify or avoid copy), const auto& for read-only access to non-trivial types.
  • Lambdas [capture](params){ body } are anonymous functions. Capture by value [x], by reference [&x], all by value [=], or all by reference [&]. Use mutable to modify by-value captures. Lambdas are essential for STL algorithm comparators and callbacks.
  • nullptr is a type-safe null pointer (replaces NULL which is just 0). enum class is a scoped enumeration with no implicit int conversion. constexpr enables compile-time evaluation of functions and variables.
  • C++14 generic lambdas use auto parameters, working like templates. Return type deduction lets the compiler infer the return type. These make lambdas as powerful as template functions.
  • C++17 structured bindings (auto [x, y] = pair) unpack pairs, tuples, and structs. If-with-initializer (if (auto x = expr; condition)) limits variable scope. std::optional represents a value that may not exist.
  • C++17 string_view is a non-owning, read-only view into string data. It avoids copies when passing strings to functions. Never let a string_view outlive its source string.
  • C++20 concepts constrain template parameters with readable requirements, replacing SFINAE with clear error messages. The spaceship operator (<=>) generates all six comparison operators from a single declaration.
  • Modern C++ follows the Rule of Zero: use smart pointers, std::string, and std::vector so the compiler-generated constructors and destructors do the right thing. No manual new/delete.
  • The evolution from C++03 to C++20 makes the language dramatically safer and more productive: auto reduces boilerplate, lambdas replace function objects, smart pointers prevent leaks, constexpr moves computation to compile time, and concepts make templates readable.

Ready to Practice?

Test your understanding with 50+ practice questions on this topic.

Go to Practice Questions

Want to learn C++ with a live mentor?

Explore our C++ Masterclass