Modern C++ Features: constexpr, Structured Bindings, optional, variant & std::format Complete Masterclass

โšก Modern C++ (C++17 / C++20 / C++23) ๐ŸŸข Lesson 20 ๐Ÿ“‚ Phase 20: Modern C++ Features ๐Ÿ“… 2026 Master Edition
๐Ÿ“Œ Covered in this in-depth guide: auto & nullptr ยท uniform brace init ยท enum class ยท constexpr & consteval ยท constinit ยท Structured Bindings ยท if/switch-init ยท decltype ยท std::format tables ยท std::chrono ยท Move Semantics ยท rvalue && ยท std::move ยท perfect forwarding

Welcome to Phase 20 (Chapter 20): Modern C++ Features Masterclass! C++11 through C++23 brought a revolution in expressiveness, safety, and performance. This chapter covers the most impactful modern features: auto, constexpr, structured bindings, std::optional, std::variant, std::format, Concepts, coroutines introduction, and Modules overview.

1C++11 Core Features
C++ โ€” auto, nullptr, range-for, enum class, uniform initโ–ถ Run in Compiler
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <memory>

int main() {
    // auto โ€” compile-time type deduction, zero overhead
    auto i = 42;                              // int
    auto d = 3.14;                            // double
    auto s = std::string{"hello"};            // std::string
    auto v = std::vector<int>{1, 2, 3, 4, 5};
    auto p = std::make_unique<int>(99);
    std::cout << "auto types: " << i << " " << d << " " << s << "
";

    // auto with references
    const auto& ref = v;    // const vector<int>&
    auto& mref = v;          // vector<int>&

    // nullptr โ€” type-safe null (replaces NULL and 0)
    int* raw = nullptr;
    void* vp = nullptr;
    if (raw == nullptr) std::cout << "raw is null
";
    // raw == 0;  // works but unclear
    // raw == NULL;  // might cause overload ambiguity

    // Uniform brace initialization โ€” prevents narrowing!
    int a{5};
    double db{3.14};
    // int bad{3.14};  // COMPILE ERROR โ€” narrowing from double to int!
    std::vector<int> vec{10, 20, 30, 40};
    std::map<std::string, int> m{{"one",1}, {"two",2}, {"three",3}};

    // Range-based for (C++11)
    std::cout << "vec: ";
    for (auto x : vec) std::cout << x << " ";
    std::cout << "
";

    // Range-based for with init (C++20)
    for (auto copy = vec; auto x : copy) std::cout << x * 2 << " ";
    std::cout << "
";

    // enum class โ€” scoped, strongly typed, no implicit int conversion
    enum class Color { Red, Green, Blue };
    enum class Direction { North, South, East, West };

    Color c = Color::Red;
    Direction d2 = Direction::North;
    // if (c == d2) {}       // COMPILE ERROR โ€” different types!
    // if (c == 0) {}        // COMPILE ERROR โ€” no implicit int conversion!
    if (c == Color::Red) std::cout << "Red!
";

    // Specify underlying type for enum class
    enum class Status : uint8_t { OK = 0, Error = 1, Pending = 2 };
    Status s2 = Status::OK;
    std::cout << "status raw: " << (int)s2 << "
";  // explicit cast OK
    return 0;
}
2constexpr, consteval & constinit
C++ โ€” constexpr, consteval, constinit (C++11/17/20)โ–ถ Run in Compiler
#include <iostream>
#include <array>
#include <cmath>

// constexpr function โ€” evaluated at compile time if inputs are compile-time
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr double circleArea(double r) {
    return 3.14159265358979 * r * r;
}

constexpr bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; ++i)
        if (n % i == 0) return false;
    return true;
}

// consteval (C++20) โ€” MUST be compile-time only
consteval int pow2(int n) {
    return 1 << n;
}

// constinit (C++20) โ€” constant initialization of static/thread_local vars
constinit int globalVal = factorial(5);  // guaranteed compile-time init

// Compile-time array using constexpr
template <int N>
constexpr auto makePrimes() {
    std::array<int, N> primes{};
    int count = 0;
    for (int i = 2; count < N; ++i) {
        if (isPrime(i)) primes[count++] = i;
    }
    return primes;
}

int main() {
    // compile-time constants
    constexpr int fact10 = factorial(10);   // 3628800 โ€” computed at compile time!
    constexpr double area5 = circleArea(5.0);
    std::cout << "10! = " << fact10 << "
";
    std::cout << "Area(r=5) = " << area5 << "
";

    // runtime usage (also valid for constexpr functions)
    int n;
    std::cin >> n;
    std::cout << "Runtime factorial(" << n << ") = " << factorial(n) << "
";

    // consteval โ€” compile-time only
    constexpr int p8 = pow2(8);     // 256 at compile time
    std::cout << "2^8 = " << p8 << "
";
    // int x; pow2(x);  // COMPILE ERROR โ€” runtime value not allowed!

    // Compile-time prime table
    constexpr auto first10Primes = makePrimes<10>();
    std::cout << "First 10 primes: ";
    for (int p : first10Primes) std::cout << p << " ";
    std::cout << "
";

    // constinit
    std::cout << "globalVal (5!) = " << globalVal << "
";
    return 0;
}
3Structured Bindings, if/switch with init, & decltype
C++ โ€” Structured bindings, if-init, decltype, auto returnโ–ถ Run in Compiler
#include <iostream>
#include <map>
#include <tuple>
#include <string>
#include <vector>
#include <optional>

// Return multiple values cleanly
struct ParseResult {
    int value;
    bool success;
    std::string error;
};

ParseResult parseNumber(const std::string& s) {
    try {
        return {std::stoi(s), true, ""};
    } catch (const std::exception& e) {
        return {0, false, e.what()};
    }
}

// decltype โ€” type of expression at compile time
template <typename T, typename U>
auto safeAdd(T a, U b) -> decltype(a + b) {
    return a + b;
}

int main() {
    // โ”€โ”€โ”€ Structured Bindings (C++17) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    // pair
    auto pair = std::make_pair(42, std::string("hello"));
    auto [num, str] = pair;
    std::cout << "pair: " << num << " " << str << "
";

    // tuple
    auto t = std::make_tuple(1, 3.14, std::string("C++20"), true);
    auto [id, pi, lang, flag] = t;
    std::cout << id << " " << pi << " " << lang << " " << flag << "
";

    // struct decomposition
    auto [val, ok, err] = parseNumber("42");
    if (ok) std::cout << "Parsed: " << val << "
";
    auto [val2, ok2, err2] = parseNumber("bad");
    if (!ok2) std::cout << "Parse error: " << err2 << "
";

    // map iteration with structured bindings
    std::map<std::string, int> scores{{"Alice",95}, {"Bob",87}, {"Charlie",92}};
    for (const auto& [name, score] : scores) {
        std::cout << name << ": " << score << "
";
    }

    // Modify via reference binding
    for (auto& [name, score] : scores) score += 5;  // give everyone +5

    // โ”€โ”€โ”€ if with initializer (C++17) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    if (auto it = scores.find("Alice"); it != scores.end()) {
        std::cout << "Alice's score: " << it->second << "
";
        // it is scoped to this if block only!
    }

    // โ”€โ”€โ”€ switch with initializer (C++17) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    switch (auto [v, s, e] = parseNumber("100"); s ? v : -1) {
        case -1:  std::cout << "Parse failed
"; break;
        case 100: std::cout << "Got 100!
"; break;
        default:  std::cout << "Got: " << v << "
"; break;
    }

    // โ”€โ”€โ”€ decltype โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    int x = 5; double y = 3.14;
    decltype(x + y) result = x + y;    // result is double
    decltype(x) copy = x;              // copy is int
    std::cout << "decltype result: " << result << "
";

    std::cout << "safeAdd(3, 4.5) = " << safeAdd(3, 4.5) << "
";
    return 0;
}
4std::format (C++20) & std::chrono
C++ โ€” std::format, std::chrono performance timingโ–ถ Run in Compiler
#include <iostream>
#include <format>       // C++20
#include <chrono>
#include <string>
#include <vector>
#include <cmath>

// Benchmark helper using chrono
template <typename Func>
double measureMs(Func&& fn, int iterations = 1) {
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i) fn();
    auto end = std::chrono::high_resolution_clock::now();
    auto dur = std::chrono::duration<double, std::milli>(end - start);
    return dur.count();
}

int main() {
    // โ”€โ”€โ”€ std::format (C++20) โ€” type-safe, expressive formatting โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    std::string s1 = std::format("Hello, {}!", "World");
    std::string s2 = std::format("Pi = {:.4f}", 3.14159265);
    std::string s3 = std::format("{:>10} | {:<10} | {:^10}", "right", "left", "center");
    std::string s4 = std::format("Hex: {:x} Oct: {:o} Bin: {:b}", 255, 255, 255);
    std::string s5 = std::format("Sci: {:e}", 1234567.89);
    std::string s6 = std::format("{0} {1} {0}", "echo", "this");  // positional

    std::cout << s1 << "
" << s2 << "
" << s3 << "
"
              << s4 << "
" << s5 << "
" << s6 << "
";

    // Table formatting
    std::cout << std::format("
{:-<30}
", "");  // separator line
    std::cout << std::format("{:<15} {:>8} {:>6}
", "Name", "Score", "Grade");
    std::cout << std::format("{:-<30}
", "");
    for (auto [name, score] : std::vector<std::pair<std::string, int>>{
            {"Alice",95}, {"Bob",87}, {"Charlie",72}}) {
        char grade = score>=90?'A': score>=80?'B': 'C';
        std::cout << std::format("{:<15} {:>8} {:>6}
", name, score, grade);
    }

    // โ”€โ”€โ”€ std::chrono โ€” time points, durations, clocks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    // High-resolution benchmark
    auto ms = measureMs([]() {
        double sum = 0;
        for (int i = 0; i < 1'000'000; ++i) sum += std::sqrt(i);
        return sum;
    });
    std::cout << std::format("
Benchmark: {:.3f} ms
", ms);

    // Duration arithmetic
    using namespace std::chrono_literals;
    auto d1 = 2h + 30min + 45s;
    std::cout << "Duration: " << std::chrono::duration_cast<std::chrono::seconds>(d1).count() << " seconds
";

    // Current time
    auto now = std::chrono::system_clock::now();
    auto time_t_now = std::chrono::system_clock::to_time_t(now);
    std::cout << "Now: " << std::ctime(&time_t_now);

    return 0;
}
5Move Semantics & Rvalue References
C++ โ€” rvalue references, std::move, perfect forwardingโ–ถ Run in Compiler
#include <iostream>
#include <string>
#include <vector>
#include <utility>

class BigBuffer {
    std::vector<int> data_;
    std::string name_;
public:
    BigBuffer(std::string name, std::size_t size)
        : data_(size, 0), name_{std::move(name)} {
        std::cout << "Constructed " << name_ << " (" << size << " ints)
";
    }

    // Copy constructor โ€” expensive
    BigBuffer(const BigBuffer& other)
        : data_{other.data_}, name_{other.name_ + "_copy"} {
        std::cout << "COPY: " << name_ << " (copying " << data_.size() << " ints)
";
    }

    // Move constructor โ€” cheap (steals resources)
    BigBuffer(BigBuffer&& other) noexcept
        : data_{std::move(other.data_)}, name_{std::move(other.name_) + "_moved"} {
        std::cout << "MOVE: " << name_ << " (zero copy!)
";
    }

    std::size_t size() const { return data_.size(); }
    const std::string& name() const { return name_; }
};

// Perfect forwarding โ€” forward args to constructor without extra copies
template <typename T, typename... Args>
T createObject(Args&&... args) {
    return T(std::forward<Args>(args)...);
}

BigBuffer makeBuffer(std::string name) {
    BigBuffer local{std::move(name), 1000};
    return local;   // NRVO (Named Return Value Optimization) โ€” likely no copy/move!
}

int main() {
    std::cout << "=== Copy vs Move ===
";
    BigBuffer b1{"original", 1000};
    BigBuffer b2{b1};                    // COPY (expensive)
    BigBuffer b3{std::move(b1)};         // MOVE (cheap!) โ€” b1 is now empty
    std::cout << "b1 size after move: " << b1.size() << "
";  // 0
    std::cout << "b3 size: " << b3.size() << "
";              // 1000

    std::cout << "
=== Return Value Optimization ===
";
    BigBuffer b4 = makeBuffer("factory");   // NRVO โ€” no move needed

    std::cout << "
=== Perfect Forwarding ===
";
    auto b5 = createObject<BigBuffer>(std::string("forwarded"), 500);

    std::cout << "
=== std::move with string ===
";
    std::string s1 = "Hello World (large string with much content)";
    std::string s2 = std::move(s1);   // move: s2 gets content, s1 becomes empty
    std::cout << "s1 empty: " << s1.empty() << "
";
    std::cout << "s2: " << s2 << "
";
    return 0;
}
6Technical FAQs

Q1: What is the difference between constexpr and consteval?

constexpr functions may run at compile-time OR runtime. consteval (C++20) mandates compile-time evaluation โ€” calling it with a runtime value is a compile error. Use consteval for pure compile-time computations (lookup tables, templates).

Q2: Why is std::format preferred over printf?

std::format is type-safe (checked at compile time), returns std::string, is extensible for custom types (via std::formatter), and doesn't use va_args. printf has no type-checking โ€” printf("%d", 3.14) is UB.

Q3: What is an rvalue reference?

T&& is an rvalue reference โ€” it binds to temporary objects. Used in move constructors and move assignments to "steal" resources from temporaries instead of copying. std::move(x) casts x to an rvalue reference, enabling the move.

Q4: What is perfect forwarding?

std::forward<T>(arg) in a template function forwards arguments with their original value category (lvalue stays lvalue, rvalue stays rvalue). Used in wrapper functions and factory templates to avoid unnecessary copies.

Q5: What are C++20 Modules?

Modules replace header files: export module mylib; declares the module, import mylib; uses it. Advantages: no multiple-inclusion issues, no macro leakage, significantly faster compilation (no re-parsing headers), and better encapsulation.