Modern C++ C++11/14/17/20/23
Advanced Features

Modern C++: Complete Guide with C++11 to C++23 Features

Master Modern C++ with comprehensive examples: C++11, C++14, C++17, C++20, and C++23 features, modern syntax, best practices, and performance improvements.

C++11
Auto & Lambdas
C++14
Generic Lambdas
C++17
Structured Binding
C++20
Concepts & Ranges
C++23
Modules
Smart Pointers

Introduction to Modern C++

Modern C++ refers to C++ standards from C++11 onward, which introduced revolutionary features that transformed how C++ is written. These features make code safer, more expressive, and more efficient.

Why Modern C++?
  • Safer code with smart pointers
  • More expressive syntax
  • Better performance with move semantics
  • Reduced boilerplate code
  • Improved type safety
  • Enhanced template programming
C++ Standards Timeline
  • C++98: First standardized version
  • C++11: Major update (Modern C++ begins)
  • C++14: Minor improvements
  • C++17: Significant new features
  • C++20: Major update (Concepts, Ranges)
  • C++23: Latest standard (Modules)
C++11 2011

Auto type deduction, lambda expressions, smart pointers, move semantics, range-based for loops

C++14 2014

Generic lambdas, variable templates, digit separators, return type deduction

C++17 2017

Structured bindings, if constexpr, fold expressions, filesystem library

C++20 2020

Concepts, ranges, coroutines, modules, three-way comparison

C++23 2023

std::expected, std::print, deducing this, stacktrace library

1. C++11: The Modern C++ Revolution

C++11 was a monumental update that fundamentally changed how C++ is written. It introduced features that make C++ safer, more expressive, and more efficient.

Key C++11 Features
// Auto type deduction
auto x = 5;            // x is int
auto y = 3.14;         // y is double
auto z = "hello";      // z is const char*

// Range-based for loops
for (auto& item : container) { }

// Lambda expressions
auto lambda = [](int x) { return x * x; };

// Smart pointers
auto ptr = std::make_unique(42);
auto shared = std::make_shared(100);

// Move semantics
std::vector v1 = {1, 2, 3};
std::vector v2 = std::move(v1); // Move, not copy
C++11 Comprehensive Examples
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include <type_traits>
#include <initializer_list>
#include <utility>

using namespace std;

// 1. Auto Type Deduction
void demonstrateAuto() {
    cout << "=== Auto Type Deduction ===" << endl;
    
    // Basic auto usage
    auto i = 42;                    // int
    auto d = 3.14159;              // double
    auto s = "Hello, Modern C++";  // const char*
    auto v = vector<int>{1, 2, 3}; // vector<int>
    
    // With references
    int x = 100;
    auto& ref = x;                 // int&
    ref = 200;
    cout << "x after auto ref: " << x << endl;
    
    // With const
    const int ci = 300;
    auto aci = ci;                 // int (const is dropped)
    const auto caci = ci;          // const int
    
    // Auto in function return type (C++14, but demonstrating here)
    auto add = [](auto a, auto b) { return a + b; };
    cout << "add(5, 3.5): " << add(5, 3.5) << endl;
    
    // Auto with structured binding (C++17, but included for completeness)
    // pair<int, string> p = {1, "one"};
    // auto [id, name] = p;
}

// 2. Range-based For Loops
void demonstrateRangeFor() {
    cout << "\n=== Range-based For Loops ===" << endl;
    
    vector<int> numbers = {1, 2, 3, 4, 5};
    
    // Read-only
    cout << "Numbers: ";
    for (const auto& num : numbers) {
        cout << num << " ";
    }
    cout << endl;
    
    // Modify elements
    cout << "Squared numbers: ";
    for (auto& num : numbers) {
        num *= num;
        cout << num << " ";
    }
    cout << endl;
    
    // With initializer list
    cout << "Chars: ";
    for (char c : {'a', 'b', 'c', 'd'}) {
        cout << c << " ";
    }
    cout << endl;
}

// 3. Lambda Expressions
void demonstrateLambdas() {
    cout << "\n=== Lambda Expressions ===" << endl;
    
    // Basic lambda
    auto greet = []() {
        cout << "Hello from lambda!" << endl;
    };
    greet();
    
    // Lambda with parameters
    auto add = [](int a, int b) {
        return a + b;
    };
    cout << "5 + 3 = " << add(5, 3) << endl;
    
    // Lambda with capture clause
    int x = 10;
    int y = 20;
    
    // Capture by value
    auto captureByValue = [x, y]() {
        cout << "Captured by value: x=" << x << ", y=" << y << endl;
    };
    captureByValue();
    
    // Capture by reference
    auto captureByRef = [&x, &y]() {
        x *= 2;
        y *= 3;
        cout << "Modified: x=" << x << ", y=" << y << endl;
    };
    captureByRef();
    cout << "After lambda: x=" << x << ", y=" << y << endl;
    
    // Capture all by value/reference
    auto captureAllValue = [=]() { return x + y; };
    auto captureAllRef = [&]() { x++; y++; };
    
    // Lambda as predicate
    vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // Count even numbers
    int evenCount = count_if(nums.begin(), nums.end(), 
                             [](int n) { return n % 2 == 0; });
    cout << "Even numbers: " << evenCount << endl;
    
    // Find numbers greater than 5
    auto it = find_if(nums.begin(), nums.end(),
                      [](int n) { return n > 5; });
    if (it != nums.end()) {
        cout << "First number > 5: " << *it << endl;
    }
    
    // Lambda with explicit return type
    auto divide = [](double a, double b) -> double {
        if (b == 0) return 0;
        return a / b;
    };
    cout << "10 / 3 = " << divide(10, 3) << endl;
}

// 4. Smart Pointers
void demonstrateSmartPointers() {
    cout << "\n=== Smart Pointers ===" << endl;
    
    // Unique pointer (exclusive ownership)
    {
        auto uniquePtr = make_unique<int>(42);
        cout << "Unique ptr value: " << *uniquePtr << endl;
        
        // Can't copy unique_ptr, but can move
        auto movedPtr = move(uniquePtr);
        if (!uniquePtr) {
            cout << "Original unique_ptr is now null" << endl;
        }
        
        // Automatically deleted when out of scope
    }
    
    // Shared pointer (shared ownership)
    {
        auto shared1 = make_shared<string>("Shared String");
        {
            auto shared2 = shared1;  // Reference count: 2
            auto shared3 = shared2;  // Reference count: 3
            cout << "Shared string: " << *shared1 
                 << ", use count: " << shared1.use_count() << endl;
        } // shared2 and shared3 destroyed, count back to 1
        
        cout << "After scope, use count: " << shared1.use_count() << endl;
    } // shared1 destroyed, string deleted
    
    // Weak pointer (non-owning reference)
    {
        auto shared = make_shared<int>(100);
        weak_ptr<int> weak = shared;
        
        cout << "\nWeak pointer demonstration:" << endl;
        cout << "Shared use count: " << shared.use_count() << endl;
        
        if (auto locked = weak.lock()) {
            cout << "Weak pointer locked successfully: " << *locked << endl;
        } else {
            cout << "Weak pointer expired" << endl;
        }
        
        // Reset shared pointer
        shared.reset();
        
        if (weak.expired()) {
            cout << "Weak pointer expired after shared reset" << endl;
        }
    }
    
    // Custom deleter
    {
        auto fileDeleter = [](FILE* f) {
            if (f) {
                fclose(f);
                cout << "File closed by custom deleter" << endl;
            }
        };
        
        unique_ptr<FILE, decltype(fileDeleter)> 
            filePtr(fopen("test.txt", "w"), fileDeleter);
        
        if (filePtr) {
            fprintf(filePtr.get(), "Test data");
            cout << "File written" << endl;
        }
        // File automatically closed by deleter
    }
}

// 5. Move Semantics and Rvalue References
class Resource {
    int* data;
    size_t size;
    
public:
    Resource(size_t s) : size(s), data(new int[s]) {
        cout << "Resource allocated: " << size << " elements" << endl;
    }
    
    // Copy constructor
    Resource(const Resource& other) : size(other.size), data(new int[other.size]) {
        copy(other.data, other.data + size, data);
        cout << "Resource copied" << endl;
    }
    
    // Move constructor
    Resource(Resource&& other) noexcept 
        : size(other.size), data(other.data) {
        other.data = nullptr;
        other.size = 0;
        cout << "Resource moved" << endl;
    }
    
    // Copy assignment
    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            copy(other.data, other.data + size, data);
            cout << "Resource copy assigned" << endl;
        }
        return *this;
    }
    
    // Move assignment
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
            cout << "Resource move assigned" << endl;
        }
        return *this;
    }
    
    ~Resource() {
        delete[] data;
        cout << "Resource destroyed" << endl;
    }
    
    void fill(int value) {
        fill_n(data, size, value);
    }
};

void demonstrateMoveSemantics() {
    cout << "\n=== Move Semantics ===" << endl;
    
    // Create resource
    Resource res1(1000000);
    res1.fill(42);
    
    // Move instead of copy
    Resource res2 = move(res1);  // Calls move constructor
    
    // Use std::move to enable moving
    vector<Resource> resources;
    resources.reserve(10);
    
    cout << "\nAdding resources to vector:" << endl;
    resources.push_back(Resource(500000));  // Temporary, moved
    resources.push_back(move(res2));       // Existing object, moved
    
    // Perfect forwarding example
    auto createResource = [](size_t size) {
        return Resource(size);
    };
    
    auto res3 = createResource(1000);  // RVO or move
}

// 6. nullptr and Strongly-typed Enums
void demonstrateNullptrAndEnums() {
    cout << "\n=== nullptr and Strongly-typed Enums ===" << endl;
    
    // nullptr instead of NULL
    int* ptr1 = nullptr;
    double* ptr2 = nullptr;
    
    if (ptr1 == nullptr) {
        cout << "Pointer is null (using nullptr)" << endl;
    }
    
    // Old enum (C++98)
    enum ColorOld { RED, GREEN, BLUE };
    ColorOld oldColor = RED;
    
    // New enum class (C++11)
    enum class ColorNew { Red, Green, Blue };
    ColorNew newColor = ColorNew::Red;
    
    enum class TrafficLight : char { Red = 'R', Yellow = 'Y', Green = 'G' };
    TrafficLight light = TrafficLight::Green;
    
    cout << "Old enum value: " << oldColor << endl;
    // cout << newColor << endl; // Error: no implicit conversion
    cout << "New enum value: " << static_cast<int>(newColor) << endl;
}

// 7. Type Traits and Static Assert
void demonstrateTypeTraits() {
    cout << "\n=== Type Traits and Static Assert ===" << endl;
    
    // Type traits
    cout << boolalpha;
    cout << "is_integral<int>: " << is_integral<int>::value << endl;
    cout << "is_floating_point<double>: " << is_floating_point<double>::value << endl;
    cout << "is_pointer<int*>: " << is_pointer<int*>::value << endl;
    cout << "is_reference<int&>: " << is_reference<int&>::value << endl;
    
    // Static assert (compile-time check)
    static_assert(sizeof(int) >= 4, "int must be at least 4 bytes");
    static_assert(is_integral<int>::value, "int must be integral type");
    
    // Enable_if for SFINAE
    template<typename T>
    typename enable_if<is_integral<T>::value, T>::type
    square(T x) {
        return x * x;
    }
    
    template<typename T>
    typename enable_if<is_floating_point<T>::value, T>::type
    square(T x) {
        return x * x;
    }
    
    cout << "square(5): " << square(5) << endl;
    cout << "square(3.14): " << square(3.14) << endl;
    // square("hello"); // Error: no matching function
}

// 8. Variadic Templates
template<typename T>
void print(T t) {
    cout << t << endl;
}

template<typename T, typename... Args>
void print(T t, Args... args) {
    cout << t << " ";
    print(args...);
}

template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // Fold expression (C++17)
}

void demonstrateVariadicTemplates() {
    cout << "\n=== Variadic Templates ===" << endl;
    
    print(1, 2.5, "hello", 'a');
    cout << "Sum: " << sum(1, 2, 3, 4, 5) << endl;
}

int main() {
    cout << "=== C++11 FEATURES DEMONSTRATION ===" << endl << endl;
    
    demonstrateAuto();
    demonstrateRangeFor();
    demonstrateLambdas();
    demonstrateSmartPointers();
    demonstrateMoveSemantics();
    demonstrateNullptrAndEnums();
    demonstrateTypeTraits();
    demonstrateVariadicTemplates();
    
    cout << "\n=== END OF C++11 DEMONSTRATION ===" << endl;
    
    return 0;
}
C++11 Best Practices:
  • Use auto when type is obvious from context
  • Prefer smart pointers over raw pointers
  • Use nullptr instead of NULL
  • Implement move semantics for resource-owning classes
  • Use enum class for type-safe enumerations
  • Prefer range-based for loops for container iteration
Common C++11 Mistakes:
  • Using auto when type isn't clear
  • Capturing everything by reference in lambdas
  • Not marking move operations as noexcept
  • Forgetting to implement move constructor/assignment
  • Using shared_ptr when unique_ptr suffices
  • Not checking if weak_ptr has expired

C++14 2. C++14: Refinements and Improvements

C++14 built upon C++11 with smaller but significant improvements that made the language more convenient and powerful.

Key C++14 Features
// Generic lambdas
auto lambda = [](auto x, auto y) { return x + y; };

// Return type deduction for normal functions
auto add(int a, int b) {
    return a + b;  // return type deduced as int
}

// Binary literals and digit separators
int binary = 0b1010'1010;
int large = 1'000'000'000;

// Variable templates
template<typename T>
constexpr T pi = T(3.1415926535897932385L);

// std::make_unique (actually C++14, not C++11)
auto ptr = std::make_unique<int>(42);
C++14 Comprehensive Examples
#include <iostream>
#include <memory>
#include <type_traits>
#include <complex>
#include <chrono>
#include <tuple>
#include <algorithm>
#include <vector>

using namespace std;

// 1. Generic Lambdas
void demonstrateGenericLambdas() {
    cout << "=== Generic Lambdas ===" << endl;
    
    // Generic lambda with auto parameters
    auto add = [](auto a, auto b) {
        return a + b;
    };
    
    cout << "add(5, 3): " << add(5, 3) << endl;
    cout << "add(2.5, 3.7): " << add(2.5, 3.7) << endl;
    cout << "add(string(\"Hello, \"), string(\"World!\")): " 
         << add(string("Hello, "), string("World!")) << endl;
    
    // Generic lambda in algorithm
    vector<int> numbers = {1, 2, 3, 4, 5};
    vector<double> doubles = {1.1, 2.2, 3.3, 4.4, 5.5};
    
    // Same lambda works with different types
    auto print = [](const auto& container) {
        for (const auto& item : container) {
            cout << item << " ";
        }
        cout << endl;
    };
    
    cout << "Numbers: ";
    print(numbers);
    
    cout << "Doubles: ";
    print(doubles);
    
    // Generic lambda with capture
    int multiplier = 2;
    auto scale = [multiplier](auto x) {
        return x * multiplier;
    };
    
    cout << "scale(5): " << scale(5) << endl;
    cout << "scale(3.14): " << scale(3.14) << endl;
}

// 2. Return Type Deduction for Normal Functions
auto add(int a, int b) {
    return a + b;  // Deduced as int
}

auto getPi() {
    return 3.141592653589793;  // Deduced as double
}

auto createVector() {
    return vector<int>{1, 2, 3, 4, 5};  // Deduced as vector<int>
}

template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

void demonstrateReturnTypeDeduction() {
    cout << "\n=== Return Type Deduction ===" << endl;
    
    cout << "add(10, 20): " << add(10, 20) << endl;
    cout << "getPi(): " << getPi() << endl;
    
    auto vec = createVector();
    cout << "Vector size: " << vec.size() << endl;
    
    cout << "multiply(5, 3.5): " << multiply(5, 3.5) << endl;
    cout << "multiply(2, 3): " << multiply(2, 3) << endl;
}

// 3. Binary Literals and Digit Separators
void demonstrateLiterals() {
    cout << "\n=== Binary Literals and Digit Separators ===" << endl;
    
    // Binary literals
    int binary = 0b10101010;
    int binaryWithSeparator = 0b1010'1010;
    
    cout << "Binary 0b10101010: " << binary << endl;
    cout << "Binary 0b1010'1010: " << binaryWithSeparator << endl;
    cout << "Binary in hex: 0x" << hex << binary << dec << endl;
    
    // Digit separators for readability
    long long bigNumber = 1'000'000'000;
    double pi = 3.141'592'653'589'793;
    long long creditCard = 1234'5678'9012'3456;
    long long bytes = 0xFF'FF'FF'FF;
    
    cout << "Big number: " << bigNumber << endl;
    cout << "Pi: " << pi << endl;
    cout << "Credit card (example): " << creditCard << endl;
    cout << "Max 32-bit: " << bytes << endl;
}

// 4. Variable Templates
template<typename T>
constexpr T pi = T(3.1415926535897932385L);

template<typename T>
constexpr T e = T(2.7182818284590452353L);

template<typename T>
constexpr T epsilon = T(1e-6);

void demonstrateVariableTemplates() {
    cout << "\n=== Variable Templates ===" << endl;
    
    cout << "Pi as float: " << pi<float> << endl;
    cout << "Pi as double: " << pi<double> << endl;
    cout << "Pi as long double: " << pi<long double> << endl;
    
    cout << "E as float: " << e<float> << endl;
    cout << "E as double: " << e<double> << endl;
    
    cout << "Epsilon for float: " << epsilon<float> << endl;
    cout << "Epsilon for double: " << epsilon<double> << endl;
}

// 5. constexpr improvements
constexpr int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int power(int base, int exp) {
    int result = 1;
    for (int i = 0; i < exp; ++i) {
        result *= base;
    }
    return result;
}

void demonstrateConstexprImprovements() {
    cout << "\n=== constexpr Improvements ===" << endl;
    
    // Can now use loops and variables in constexpr functions
    constexpr int fact5 = factorial(5);
    constexpr int pow2_10 = power(2, 10);
    
    cout << "5! = " << fact5 << endl;
    cout << "2^10 = " << pow2_10 << endl;
    
    // Can be used at compile time
    int array1[factorial(5)];  // OK in C++14
    int array2[power(2, 3)];   // OK in C++14
    
    cout << "Size of array1: " << sizeof(array1) / sizeof(array1[0]) << endl;
    cout << "Size of array2: " << sizeof(array2) / sizeof(array2[0]) << endl;
}

// 6. std::make_unique (actually C++14)
class Widget {
    int id;
public:
    Widget(int i) : id(i) {
        cout << "Widget " << id << " created" << endl;
    }
    
    ~Widget() {
        cout << "Widget " << id << " destroyed" << endl;
    }
    
    void doSomething() const {
        cout << "Widget " << id << " doing something" << endl;
    }
};

void demonstrateMakeUnique() {
    cout << "\n=== std::make_unique ===" << endl;
    
    // C++11 way (before make_unique)
    // unique_ptr<Widget> ptr1(new Widget(1));
    
    // C++14 way (with make_unique)
    auto ptr1 = make_unique<Widget>(1);
    auto ptr2 = make_unique<Widget>(2);
    
    ptr1->doSomething();
    ptr2->doSomething();
    
    // Array version
    auto arrayPtr = make_unique<Widget[]>(3);
    
    // With custom deleter
    auto customPtr = unique_ptr<Widget, void(*)(Widget*)>(
        new Widget(99),
        [](Widget* w) {
            cout << "Custom deleter called" << endl;
            delete w;
        }
    );
}

// 7. Heterogeneous Lookup in Associative Containers
struct StringCompare {
    using is_transparent = void;
    
    bool operator()(const string& a, const string& b) const {
        return a < b;
    }
    
    bool operator()(const string& a, const char* b) const {
        return a < b;
    }
    
    bool operator()(const char* a, const string& b) const {
        return a < b;
    }
};

void demonstrateHeterogeneousLookup() {
    cout << "\n=== Heterogeneous Lookup ===" << endl;
    
    // Set with transparent comparator
    set<string, StringCompare> words = {"apple", "banana", "cherry"};
    
    // Can find with string or const char*
    auto it1 = words.find("banana");
    if (it1 != words.end()) {
        cout << "Found with const char*: " << *it1 << endl;
    }
    
    string key = "apple";
    auto it2 = words.find(key);
    if (it2 != words.end()) {
        cout << "Found with string: " << *it2 << endl;
    }
    
    // Also works with map
    map<string, int, StringCompare> wordCount = {
        {"apple", 5},
        {"banana", 3},
        {"cherry", 8}
    };
    
    auto it3 = wordCount.find("cherry");
    if (it3 != wordCount.end()) {
        cout << "Cherry count: " << it3->second << endl;
    }
}

// 8. std::integer_sequence
template<typename T, T... Ints>
void print_sequence(integer_sequence<T, Ints...>) {
    ((cout << Ints << " "), ...);
    cout << endl;
}

template<typename T, T N>
using make_integer_sequence = std::make_integer_sequence<T, N>;

void demonstrateIntegerSequence() {
    cout << "\n=== std::integer_sequence ===" << endl;
    
    print_sequence(integer_sequence<int, 1, 2, 3, 4, 5>{});
    print_sequence(make_integer_sequence<int, 10>{});
    
    // Useful for template metaprogramming
    auto seq = make_index_sequence<5>{};
    cout << "Index sequence: ";
    print_sequence(seq);
}

int main() {
    cout << "=== C++14 FEATURES DEMONSTRATION ===" << endl << endl;
    
    demonstrateGenericLambdas();
    demonstrateReturnTypeDeduction();
    demonstrateLiterals();
    demonstrateVariableTemplates();
    demonstrateConstexprImprovements();
    demonstrateMakeUnique();
    demonstrateHeterogeneousLookup();
    demonstrateIntegerSequence();
    
    cout << "\n=== END OF C++14 DEMONSTRATION ===" << endl;
    
    return 0;
}

C++14 Impact

  • Generic lambdas reduce code duplication
  • Return type deduction simplifies function definitions
  • Digit separators improve code readability
  • Variable templates enable more generic code
  • Extended constexpr allows more compile-time computation
  • std::make_unique completes smart pointer utilities

C++17 3. C++17: Major Feature Additions

C++17 added significant features that make C++ more expressive, safe, and convenient for modern programming.

Structured Bindings
std::pair<int, std::string> p{1, "one"};
auto [id, name] = p;
// id = 1, name = "one"

std::map<int, std::string> m{{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) {
    // Use key and value
}
if constexpr
template<typename T>
void process(T value) {
    if constexpr (is_integral_v<T>) {
        // Compile-time branch
        cout << "Integer: " << value;
    } else if constexpr (is_floating_point_v<T>) {
        cout << "Float: " << value;
    } else {
        cout << "Other: " << value;
    }
}
Fold Expressions
template<typename... Args>
auto sum(Args... args) {
    return (... + args); // Fold expression
}

template<typename... Args>
bool allTrue(Args... args) {
    return (... && args); // Logical AND fold
}
C++17 Comprehensive Examples
#include <iostream>
#include <tuple>
#include <variant>
#include <optional>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <filesystem>
#include <any>
#include <type_traits>
#include <numeric>

using namespace std;
namespace fs = filesystem;

// 1. Structured Bindings
void demonstrateStructuredBindings() {
    cout << "=== Structured Bindings ===" << endl;
    
    // With pair
    pair<int, string> p{42, "answer"};
    auto [num, str] = p;
    cout << "Pair: " << num << ", " << str << endl;
    
    // With tuple
    tuple<int, string, double> t{1, "pi", 3.14};
    auto [id, name, value] = t;
    cout << "Tuple: " << id << ", " << name << ", " << value << endl;
    
    // With array
    int arr[] = {10, 20, 30};
    auto [a, b, c] = arr;
    cout << "Array: " << a << ", " << b << ", " << c << endl;
    
    // With struct
    struct Point { int x; int y; string name; };
    Point point{100, 200, "origin"};
    auto [x, y, name] = point;
    cout << "Point: " << x << ", " << y << ", " << name << endl;
    
    // In range-based for loops with maps
    map<int, string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    cout << "Map contents:" << endl;
    for (const auto& [key, value] : m) {
        cout << "  " << key << ": " << value << endl;
    }
    
    // With references
    auto& [refNum, refStr] = p;
    refNum = 100;
    refStr = "modified";
    cout << "Modified pair: " << p.first << ", " << p.second << endl;
}

// 2. if constexpr
template<typename T>
void processValue(T value) {
    if constexpr (is_integral_v<T>) {
        cout << "Integer processing: " << value * 2 << endl;
    } else if constexpr (is_floating_point_v<T>) {
        cout << "Float processing: " << value + 1.0 << endl;
    } else if constexpr (is_same_v<T, string>) {
        cout << "String processing: " << value.length() << " chars" << endl;
    } else {
        cout << "Generic processing: " << value << endl;
    }
}

template<typename T>
auto getSize(const T& container) {
    if constexpr (has_size<T>::value) {
        return container.size();
    } else {
        return sizeof(container);
    }
}

void demonstrateIfConstexpr() {
    cout << "\n=== if constexpr ===" << endl;
    
    processValue(42);
    processValue(3.14);
    processValue(string("hello"));
    
    // Different code paths compiled based on type
    vector<int> v{1, 2, 3};
    cout << "Vector size: " << getSize(v) << endl;
    
    int x = 100;
    cout << "Int size: " << getSize(x) << endl;
}

// 3. Fold Expressions
template<typename... Args>
auto sum(Args... args) {
    return (... + args);  // Left fold: ((arg1 + arg2) + arg3) + ...
}

template<typename... Args>
auto sumRight(Args... args) {
    return (args + ...);  // Right fold: arg1 + (arg2 + (arg3 + ...))
}

template<typename... Args>
bool allTrue(Args... args) {
    return (... && args);
}

template<typename... Args>
bool anyTrue(Args... args) {
    return (... || args);
}

template<typename T, typename... Args>
void printAll(T first, Args... args) {
    cout << first;
    ((cout << ", " << args), ...);
    cout << endl;
}

void demonstrateFoldExpressions() {
    cout << "\n=== Fold Expressions ===" << endl;
    
    cout << "Sum: " << sum(1, 2, 3, 4, 5) << endl;
    cout << "Sum with doubles: " << sum(1.1, 2.2, 3.3) << endl;
    cout << "Sum with strings: " << sum(string("a"), string("b"), string("c")) << endl;
    
    cout << boolalpha;
    cout << "allTrue(true, true, true): " << allTrue(true, true, true) << endl;
    cout << "allTrue(true, false, true): " << allTrue(true, false, true) << endl;
    
    cout << "anyTrue(false, false, true): " << anyTrue(false, false, true) << endl;
    cout << "anyTrue(false, false, false): " << anyTrue(false, false, false) << endl;
    
    cout << "Printing: ";
    printAll(1, 2.5, "three", '4');
}

// 4. std::optional
optional<double> safeDivide(double a, double b) {
    if (b == 0) {
        return nullopt;  // No value
    }
    return a / b;
}

optional<string> findName(const map<int, string>& m, int key) {
    if (auto it = m.find(key); it != m.end()) {
        return it->second;
    }
    return nullopt;
}

void demonstrateOptional() {
    cout << "\n=== std::optional ===" << endl;
    
    auto result1 = safeDivide(10.0, 2.0);
    auto result2 = safeDivide(10.0, 0.0);
    
    if (result1) {
        cout << "Division successful: " << *result1 << endl;
    }
    
    if (!result2) {
        cout << "Division by zero failed" << endl;
    }
    
    // Using value_or for default
    cout << "Result1 or default: " << result1.value_or(0.0) << endl;
    cout << "Result2 or default: " << result2.value_or(0.0) << endl;
    
    // With structured binding
    map<int, string> names{{1, "Alice"}, {2, "Bob"}};
    
    if (auto name = findName(names, 1)) {
        cout << "Found: " << *name << endl;
    }
    
    if (auto name = findName(names, 99)) {
        cout << "Found: " << *name << endl;
    } else {
        cout << "Not found" << endl;
    }
}

// 5. std::variant
using Number = variant<int, double, string>;

void printNumber(const Number& num) {
    visit([](auto& value) {
        using T = decay_t<decltype(value)>;
        if constexpr (is_same_v<T, int>) {
            cout << "Integer: " << value << endl;
        } else if constexpr (is_same_v<T, double>) {
            cout << "Double: " << value << endl;
        } else if constexpr (is_same_v<T, string>) {
            cout << "String: " << value << endl;
        }
    }, num);
}

void demonstrateVariant() {
    cout << "\n=== std::variant ===" << endl;
    
    variant<int, double, string> v;
    
    v = 42;
    printNumber(v);
    
    v = 3.14;
    printNumber(v);
    
    v = "hello";
    printNumber(v);
    
    // Check what's currently stored
    if (holds_alternative<int>(v)) {
        cout << "Currently holds int" << endl;
    } else if (holds_alternative<string>(v)) {
        cout << "Currently holds string" << endl;
    }
    
    // Get the value (throws bad_variant_access if wrong type)
    try {
        auto str = get<string>(v);
        cout << "Got string: " << str << endl;
        
        // This will throw
        auto dbl = get<double>(v);
    } catch (const bad_variant_access& e) {
        cout << "Exception: " << e.what() << endl;
    }
    
    // Get by index
    cout << "Index: " << v.index() << endl;
}

// 6. std::any
void demonstrateAny() {
    cout << "\n=== std::any ===" << endl;
    
    any a = 42;
    cout << "Type: " << a.type().name() << endl;
    cout << "Value: " << any_cast<int>(a) << endl;
    
    a = string("hello");
    cout << "Type: " << a.type().name() << endl;
    cout << "Value: " << any_cast<string>(a) << endl;
    
    a = 3.14;
    
    // Check type
    if (a.has_value()) {
        cout << "Has value of type: " << a.type().name() << endl;
    }
    
    // Reset
    a.reset();
    if (!a.has_value()) {
        cout << "No value after reset" << endl;
    }
    
    // Safe any_cast
    a = 100;
    if (auto ptr = any_cast<int>(&a)) {
        cout << "Pointer to int: " << *ptr << endl;
    }
    
    if (auto ptr = any_cast<double>(&a)) {
        cout << "Pointer to double: " << *ptr << endl;
    } else {
        cout << "Not a double" << endl;
    }
}

// 7. Filesystem Library
void demonstrateFilesystem() {
    cout << "\n=== Filesystem Library ===" << endl;
    
    // Create directory
    fs::create_directory("test_dir");
    
    // Create file
    ofstream("test_dir/test.txt") << "Test content";
    
    // Check if exists
    if (fs::exists("test_dir/test.txt")) {
        cout << "File exists" << endl;
    }
    
    // Get file size
    if (auto size = fs::file_size("test_dir/test.txt"); size != static_cast<uintmax_t>(-1)) {
        cout << "File size: " << size << " bytes" << endl;
    }
    
    // Iterate directory
    cout << "Directory contents:" << endl;
    for (const auto& entry : fs::directory_iterator("test_dir")) {
        cout << "  " << entry.path().filename() << endl;
    }
    
    // Get current path
    cout << "Current path: " << fs::current_path() << endl;
    
    // Cleanup
    fs::remove_all("test_dir");
}

// 8. Parallel Algorithms
void demonstrateParallelAlgorithms() {
    cout << "\n=== Parallel Algorithms ===" << endl;
    
    vector<int> data(1000000);
    iota(data.begin(), data.end(), 0);
    
    // Sequential sort
    auto seq_data = data;
    auto start = chrono::high_resolution_clock::now();
    sort(seq_data.begin(), seq_data.end());
    auto end = chrono::high_resolution_clock::now();
    auto seq_time = chrono::duration_cast<chrono::milliseconds>(end - start);
    
    // Parallel sort (if supported)
    auto par_data = data;
    start = chrono::high_resolution_clock::now();
    sort(execution::par, par_data.begin(), par_data.end());
    end = chrono::high_resolution_clock::now();
    auto par_time = chrono::duration_cast<chrono::milliseconds>(end - start);
    
    cout << "Sequential sort time: " << seq_time.count() << "ms" << endl;
    cout << "Parallel sort time: " << par_time.count() << "ms" << endl;
    
    // Other parallel algorithms
    vector<int> numbers(1000);
    iota(numbers.begin(), numbers.end(), 1);
    
    // Parallel transform
    vector<int> squares(numbers.size());
    transform(execution::par, numbers.begin(), numbers.end(), 
              squares.begin(), [](int n) { return n * n; });
    
    // Parallel reduce
    auto sum = reduce(execution::par, numbers.begin(), numbers.end());
    cout << "Sum of 1..1000: " << sum << endl;
}

// 9. Inline Variables
// Header file would have:
// inline constexpr double pi = 3.141592653589793;
// inline vector<string> defaultNames = {"Alice", "Bob", "Charlie"};

inline constexpr double inline_pi = 3.141592653589793;

class MyClass {
public:
    inline static int counter = 0;  // Inline static member
    MyClass() { counter++; }
};

void demonstrateInlineVariables() {
    cout << "\n=== Inline Variables ===" << endl;
    
    cout << "Inline pi: " << inline_pi << endl;
    
    MyClass obj1, obj2, obj3;
    cout << "Objects created: " << MyClass::counter << endl;
}

int main() {
    cout << "=== C++17 FEATURES DEMONSTRATION ===" << endl << endl;
    
    demonstrateStructuredBindings();
    demonstrateIfConstexpr();
    demonstrateFoldExpressions();
    demonstrateOptional();
    demonstrateVariant();
    demonstrateAny();
    demonstrateFilesystem();
    demonstrateParallelAlgorithms();
    demonstrateInlineVariables();
    
    cout << "\n=== END OF C++17 DEMONSTRATION ===" << endl;
    
    return 0;
}
C++11 Type Safety
  • Smart pointers
  • nullptr
  • enum class
  • type traits
  • Better but limited
C++17 Type Safety
  • std::optional
  • std::variant
  • std::any
  • Structured bindings
  • Much more comprehensive

C++20 4. C++20: The Next Major Revolution

C++20 introduced game-changing features that fundamentally transform template programming, ranges, concurrency, and module systems.

Key C++20 Features
// Concepts
template<typename T>
concept Integral = is_integral_v<T>;

template<Integral T>
T square(T x) { return x * x; }

// Ranges
vector<int> numbers{1, 2, 3, 4, 5};
auto even = numbers | views::filter([](int n){ return n % 2 == 0; });

// Coroutines
generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i)
        co_yield i;
}

// Three-way comparison (spaceship operator)
auto operator<=>(const Point&) const = default;
C++20 Comprehensive Examples
#include <iostream>
#include <concepts>
#include <ranges>
#include <vector>
#include <algorithm>
#include <compare>
#include <coroutine>
#include <format>
#include <span>
#include <bit>
#include <numbers>
#include <chrono>
#include <source_location>

using namespace std;

// 1. Concepts
template<typename T>
concept Arithmetic = is_arithmetic_v<T>;

template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> convertible_to<T>;
};

template<typename T>
concept Container = requires(T t) {
    typename T::value_type;
    typename T::iterator;
    typename T::const_iterator;
    { t.begin() } -> same_as<typename T::iterator>;
    { t.end() } -> same_as<typename T::iterator>;
    { t.size() } -> integral;
};

// Using concepts
template<Arithmetic T>
T square(T x) {
    return x * x;
}

template<Addable T>
T addTwice(T a, T b) {
    return a + a + b + b;
}

template<Container C>
void printContainer(const C& container) {
    for (const auto& item : container) {
        cout << item << " ";
    }
    cout << endl;
}

// Requires clause
template<typename T>
requires integral<T> || floating_point<T>
T absolute(T value) {
    return value < 0 ? -value : value;
}

void demonstrateConcepts() {
    cout << "=== Concepts ===" << endl;
    
    cout << "square(5): " << square(5) << endl;
    cout << "square(3.14): " << square(3.14) << endl;
    // square("hello"); // Error: doesn't satisfy Arithmetic
    
    cout << "addTwice(10, 20): " << addTwice(10, 20) << endl;
    cout << "addTwice(string(\"a\"), string(\"b\")): " 
         << addTwice(string("a"), string("b")) << endl;
    
    vector<int> v{1, 2, 3, 4, 5};
    printContainer(v);
    
    cout << "absolute(-5): " << absolute(-5) << endl;
    cout << "absolute(-3.14): " << absolute(-3.14) << endl;
}

// 2. Ranges
void demonstrateRanges() {
    cout << "\n=== Ranges ===" << endl;
    
    vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // Filter even numbers
    auto even_numbers = numbers | views::filter([](int n) { return n % 2 == 0; });
    
    cout << "Even numbers: ";
    for (int n : even_numbers) {
        cout << n << " ";
    }
    cout << endl;
    
    // Transform (square)
    auto squares = numbers | views::transform([](int n) { return n * n; });
    
    cout << "Squares: ";
    for (int n : squares) {
        cout << n << " ";
    }
    cout << endl;
    
    // Take first 5
    auto first5 = numbers | views::take(5);
    cout << "First 5: ";
    ranges::copy(first5, ostream_iterator<int>(cout, " "));
    cout << endl;
    
    // Drop first 3
    auto after3 = numbers | views::drop(3);
    cout << "After 3: ";
    ranges::copy(after3, ostream_iterator<int>(cout, " "));
    cout << endl;
    
    // Reverse
    auto reversed = numbers | views::reverse;
    cout << "Reversed: ";
    ranges::copy(reversed, ostream_iterator<int>(cout, " "));
    cout << endl;
    
    // Pipeline: filter even, square them, take first 3
    auto pipeline = numbers 
        | views::filter([](int n) { return n % 2 == 0; })
        | views::transform([](int n) { return n * n; })
        | views::take(3);
    
    cout << "Pipeline (even squared first 3): ";
    for (int n : pipeline) {
        cout << n << " ";
    }
    cout << endl;
    
    // Range algorithms
    cout << "Max: " << ranges::max(numbers) << endl;
    cout << "Min: " << ranges::min(numbers) << endl;
    cout << "Sum: " << ranges::fold_left(numbers, 0, plus<>{}) << endl;
    
    // Range-based sort
    vector<int> to_sort{5, 2, 8, 1, 9, 3};
    ranges::sort(to_sort);
    cout << "Sorted: ";
    ranges::copy(to_sort, ostream_iterator<int>(cout, " "));
    cout << endl;
}

// 3. Coroutines (simplified example)
#if __has_include(<coroutine>)
#include <coroutine>

generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;
    }
}

generator<int> fibonacci(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        int temp = a;
        a = b;
        b = temp + b;
    }
}

void demonstrateCoroutines() {
    cout << "\n=== Coroutines ===" << endl;
    
    cout << "Range 1-5: ";
    for (int i : range(1, 6)) {
        cout << i << " ";
    }
    cout << endl;
    
    cout << "First 10 Fibonacci numbers: ";
    for (int i : fibonacci(10)) {
        cout << i << " ";
    }
    cout << endl;
}
#else
void demonstrateCoroutines() {
    cout << "\n=== Coroutines (not supported in this compiler) ===" << endl;
}
#endif

// 4. Three-way Comparison (Spaceship Operator)
class Point {
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}
    
    // Auto-generate all comparison operators
    auto operator<=>(const Point& other) const = default;
    
    // Or customize
    /*
    auto operator<=>(const Point& other) const {
        if (auto cmp = x <=> other.x; cmp != 0) return cmp;
        return y <=> other.y;
    }
    */
    
    friend ostream& operator<<(ostream& os, const Point& p) {
        return os << "(" << p.x << ", " << p.y << ")";
    }
};

void demonstrateSpaceship() {
    cout << "\n=== Three-way Comparison ===" << endl;
    
    Point p1{1, 2};
    Point p2{1, 2};
    Point p3{2, 1};
    Point p4{1, 3};
    
    cout << boolalpha;
    cout << p1 << " == " << p2 << ": " << (p1 == p2) << endl;
    cout << p1 << " != " << p3 << ": " << (p1 != p3) << endl;
    cout << p1 << " < " << p3 << ": " << (p1 < p3) << endl;
    cout << p1 << " <= " << p4 << ": " << (p1 <= p4) << endl;
    cout << p3 << " > " << p1 << ": " << (p3 > p1) << endl;
    cout << p3 << " >= " << p4 << ": " << (p3 >= p4) << endl;
    
    // Direct three-way comparison
    auto result = p1 <=> p3;
    if (result < 0) cout << "p1 < p3" << endl;
    else if (result > 0) cout << "p1 > p3" << endl;
    else cout << "p1 == p3" << endl;
}

// 5. std::format
void demonstrateFormat() {
    cout << "\n=== std::format ===" << endl;
    
    string formatted;
    
    // Basic formatting
    formatted = format("Hello, {}!", "World");
    cout << formatted << endl;
    
    // Number formatting
    formatted = format("Integer: {}, Float: {:.2f}, Scientific: {:.2e}", 
                       42, 3.14159, 123456.789);
    cout << formatted << endl;
    
    // Positional arguments
    formatted = format("{1} {0} {1}", "a", "b");
    cout << formatted << endl;
    
    // Named arguments (C++20 library extension)
    // formatted = format("Name: {name}, Age: {age}", 
    //                    arg("name", "Alice"), arg("age", 30));
    
    // Width and alignment
    formatted = format("|{:<10}|{:^10}|{:>10}|", "left", "center", "right");
    cout << formatted << endl;
    
    // Different bases
    formatted = format("Decimal: {}, Hex: {:x}, Binary: {:b}", 255, 255, 255);
    cout << formatted << endl;
    
    // Chrono formatting
    auto now = chrono::system_clock::now();
    formatted = format("Current time: {:%Y-%m-%d %H:%M:%S}", now);
    cout << formatted << endl;
}

// 6. std::span
void processSpan(span<const int> data) {
    cout << "Span size: " << data.size() << endl;
    cout << "Span data: ";
    for (int value : data) {
        cout << value << " ";
    }
    cout << endl;
    
    // Subspan
    auto sub = data.subspan(1, 3);
    cout << "Subspan: ";
    for (int value : sub) {
        cout << value << " ";
    }
    cout << endl;
}

void demonstrateSpan() {
    cout << "\n=== std::span ===" << endl;
    
    // From array
    int arr[] = {1, 2, 3, 4, 5};
    processSpan(arr);
    
    // From vector
    vector<int> vec{10, 20, 30, 40, 50};
    processSpan(vec);
    
    // From initializer list (needs to be stored first)
    vector<int> init_vec{100, 200, 300};
    processSpan(init_vec);
    
    // Modifying through span
    span<int> mutable_span{arr};
    mutable_span[0] = 999;
    cout << "Modified first element: " << arr[0] << endl;
    
    // Static extent span
    span<int, 5> static_span{arr};  // Size known at compile time
    cout << "Static span size: " << static_span.size() << endl;
}

// 7. Bit Operations
void demonstrateBitOperations() {
    cout << "\n=== Bit Operations ===" << endl;
    
    uint32_t x = 0b10101010;
    
    cout << "x: " << bitset<8>(x) << endl;
    cout << "popcount(x): " << popcount(x) << endl;
    cout << "has_single_bit(x): " << boolalpha << has_single_bit(x) << endl;
    cout << "bit_width(x): " << bit_width(x) << endl;
    cout << "rotl(x, 2): " << bitset<8>(rotl(x, 2)) << endl;
    cout << "rotr(x, 2): " << bitset<8>(rotr(x, 2)) << endl;
    
    // Endianness
    cout << "\nEndianness: ";
    if constexpr (endian::native == endian::little) {
        cout << "Little endian" << endl;
    } else if constexpr (endian::native == endian::big) {
        cout << "Big endian" << endl;
    } else {
        cout << "Mixed endian" << endl;
    }
    
    // Bit cast
    float f = 3.14f;
    auto bits = bit_cast<uint32_t>(f);
    cout << "Float bits: " << hex << bits << dec << endl;
}

// 8. Mathematical Constants
void demonstrateMathConstants() {
    cout << "\n=== Mathematical Constants ===" << endl;
    
    using namespace numbers;
    
    cout << "pi: " << pi << endl;
    cout << "e: " << e << endl;
    cout << "sqrt(2): " << sqrt2 << endl;
    cout << "sqrt(3): " << sqrt3 << endl;
    cout << "phi (golden ratio): " << phi << endl;
    cout << "ln(2): " << ln2 << endl;
    cout << "ln(10): " << ln10 << endl;
}

// 9. Source Location
void log(const string& message, 
         const source_location& location = source_location::current()) {
    cout << format("[{}:{}:{}] {}",
                   location.file_name(),
                   location.line(),
                   location.column(),
                   message) << endl;
}

void demonstrateSourceLocation() {
    cout << "\n=== Source Location ===" << endl;
    
    log("This is a log message");
    
    auto my_location = source_location::current();
    cout << "File: " << my_location.file_name() << endl;
    cout << "Line: " << my_location.line() << endl;
    cout << "Column: " << my_location.column() << endl;
    cout << "Function: " << my_location.function_name() << endl;
}

int main() {
    cout << "=== C++20 FEATURES DEMONSTRATION ===" << endl << endl;
    
    demonstrateConcepts();
    demonstrateRanges();
    demonstrateCoroutines();
    demonstrateSpaceship();
    demonstrateFormat();
    demonstrateSpan();
    demonstrateBitOperations();
    demonstrateMathConstants();
    demonstrateSourceLocation();
    
    cout << "\n=== END OF C++20 DEMONSTRATION ===" << endl;
    
    return 0;
}
C++20 Best Practices:
  • Use concepts to constrain templates for better error messages
  • Prefer ranges algorithms over traditional STL algorithms
  • Use std::format for type-safe string formatting
  • Use std::span for non-owning views of contiguous data
  • Default the spaceship operator when appropriate
  • Use coroutines for asynchronous and generator patterns
  • Use modules for better compilation times and encapsulation

C++23 5. C++23: The Latest Standard

C++23 continues the evolution with practical improvements, new library features, and enhancements to existing functionality.

Key C++23 Features
// std::expected for error handling
std::expected<int, std::string> parseNumber(std::string_view s);

// std::print for easier output
std::print("Hello, {}!", name);

// Deducing this
struct Widget {
    void process(this auto&& self) {
        // self is deduced
    }
};

// std::stacktrace for debugging
auto trace = std::stacktrace::current();
C++23 Comprehensive Examples
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <format>

// Note: Some C++23 features may not be fully implemented in all compilers
// This code demonstrates the concepts even if not compilable everywhere

using namespace std;

// 1. std::expected (Error handling without exceptions)
/*
#include <expected>

enum class ParseError {
    InvalidFormat,
    OutOfRange,
    EmptyString
};

expected<int, ParseError> parseInteger(string_view str) {
    if (str.empty()) {
        return unexpected{ParseError::EmptyString};
    }
    
    int result = 0;
    auto [ptr, ec] = from_chars(str.data(), str.data() + str.size(), result);
    
    if (ec == errc::invalid_argument) {
        return unexpected{ParseError::InvalidFormat};
    }
    if (ec == errc::result_out_of_range) {
        return unexpected{ParseError::OutOfRange};
    }
    
    return result;
}

void demonstrateExpected() {
    cout << "=== std::expected ===" << endl;
    
    auto result1 = parseInteger("42");
    if (result1) {
        cout << "Parsed: " << *result1 << endl;
    } else {
        cout << "Error: " << static_cast<int>(result1.error()) << endl;
    }
    
    auto result2 = parseInteger("abc");
    if (!result2) {
        cout << "Failed to parse 'abc': " 
             << static_cast<int>(result2.error()) << endl;
    }
    
    // Transform if valid
    auto squared = result1.transform([](int n) { return n * n; });
    if (squared) {
        cout << "Squared: " << *squared << endl;
    }
    
    // Or provide default
    auto value = result2.value_or(0);
    cout << "Default value: " << value << endl;
}
*/

// 2. std::print (Improved output)
void demonstratePrint() {
    cout << "\n=== std::print ===" << endl;
    
    // std::print("Hello, {}!\n", "World");
    // std::println("The answer is {}", 42);
    
    // With files
    // std::print(stdout, "Writing to stdout\n");
    // std::print(stderr, "Error message\n");
    
    // cout << "Note: std::print not available in this compiler" << endl;
    cout << "Using std::format as alternative: "
         << format("Hello, {}! The answer is {}\n", "World", 42);
}

// 3. Deducing this (Explicit object parameter)
/*
struct Widget {
    int value = 0;
    
    // Traditional member function
    void setValue(int v) & {
        value = v;
    }
    
    // With deducing this
    void setValue(this auto&& self, int v) {
        self.value = v;
    }
    
    // Const-correct automatically
    int getValue() const {
        return value;
    }
    
    // With deducing this
    int getValue(this const auto& self) {
        return self.value;
    }
};

void demonstrateDeducingThis() {
    cout << "\n=== Deducing this ===" << endl;
    
    Widget w;
    w.setValue(42);
    cout << "Value: " << w.getValue() << endl;
    
    const Widget cw{100};
    // cw.setValue(200);  // Error: const object
    cout << "Const value: " << cw.getValue() << endl;
    
    // Works with inheritance
    struct Derived : Widget {
        using Widget::setValue;
        using Widget::getValue;
    };
    
    Derived d;
    d.setValue(99);
    cout << "Derived value: " << d.getValue() << endl;
}
*/

// 4. std::stacktrace (Runtime stack traces)
/*
#include <stacktrace>

void deepFunction(int level) {
    if (level > 0) {
        deepFunction(level - 1);
    } else {
        auto trace = stacktrace::current();
        cout << "Stack trace (" << trace.size() << " frames):\n";
        for (const auto& entry : trace) {
            cout << "  " << entry.description() 
                 << " at " << entry.source_file() 
                 << ":" << entry.source_line() << endl;
        }
    }
}

void demonstrateStacktrace() {
    cout << "\n=== std::stacktrace ===" << endl;
    deepFunction(3);
}
*/

// 5. std::mdspan (Multidimensional array view)
/*
#include <mdspan>

void demonstrateMdspan() {
    cout << "\n=== std::mdspan ===" << endl;
    
    vector<int> data(24);
    iota(data.begin(), data.end(), 0);
    
    // 3D array: 2 x 3 x 4
    mdspan mat3d(data.data(), 2, 3, 4);
    
    cout << "3D array (2x3x4):\n";
    for (int i = 0; i < mat3d.extent(0); ++i) {
        cout << "  Layer " << i << ":\n";
        for (int j = 0; j < mat3d.extent(1); ++j) {
            cout << "    ";
            for (int k = 0; k < mat3d.extent(2); ++k) {
                cout << setw(3) << mat3d[i, j, k] << " ";
            }
            cout << endl;
        }
    }
}
*/

// 6. Flat Containers (std::flat_map, std::flat_set)
/*
#include <flat_map>

void demonstrateFlatContainers() {
    cout << "\n=== Flat Containers ===" << endl;
    
    flat_map<string, int> ages;
    ages.insert({"Alice", 30});
    ages.insert({"Bob", 25});
    ages.insert({"Charlie", 35});
    
    cout << "Ages:\n";
    for (const auto& [name, age] : ages) {
        cout << "  " << name << ": " << age << endl;
    }
    
    // Better cache locality than std::map
    // Faster iteration, slower insertion
}
*/

// 7. std::generator (Coroutine-based generators)
/*
#include <generator>

generator<int> range(int start, int stop, int step = 1) {
    for (int i = start; i < stop; i += step) {
        co_yield i;
    }
}

generator<pair<int, int>> enumerate(span<const int> data) {
    for (size_t i = 0; i < data.size(); ++i) {
        co_yield pair{i, data[i]};
    }
}

void demonstrateGenerator() {
    cout << "\n=== std::generator ===" << endl;
    
    cout << "Range 1-5: ";
    for (int i : range(1, 6)) {
        cout << i << " ";
    }
    cout << endl;
    
    cout << "Even numbers 0-10: ";
    for (int i : range(0, 11, 2)) {
        cout << i << " ";
    }
    cout << endl;
    
    vector<int> data{10, 20, 30, 40};
    cout << "Enumerated:\n";
    for (auto [index, value] : enumerate(data)) {
        cout << "  [" << index << "] = " << value << endl;
    }
}
*/

// 8. std::byteswap (Endian conversion)
void demonstrateByteswap() {
    cout << "\n=== std::byteswap ===" << endl;
    
    uint16_t x = 0x1234;
    uint32_t y = 0x12345678;
    uint64_t z = 0x123456789ABCDEF0;
    
    /*
    cout << hex;
    cout << "Original x: 0x" << x << endl;
    cout << "Byteswapped x: 0x" << byteswap(x) << endl;
    
    cout << "Original y: 0x" << y << endl;
    cout << "Byteswapped y: 0x" << byteswap(y) << endl;
    
    cout << "Original z: 0x" << z << endl;
    cout << "Byteswapped z: 0x" << byteswap(z) << endl;
    cout << dec;
    */
    
    cout << "Note: std::byteswap not available in this compiler" << endl;
}

// 9. New Algorithms
void demonstrateNewAlgorithms() {
    cout << "\n=== New Algorithms ===" << endl;
    
    vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // std::shift_left, std::shift_right
    /*
    shift_left(data.begin(), data.end(), 2);
    cout << "After shift_left by 2: ";
    for (int n : data) cout << n << " ";
    cout << endl;
    
    shift_right(data.begin(), data.end(), 3);
    cout << "After shift_right by 3: ";
    for (int n : data) cout << n << " ";
    cout << endl;
    */
    
    // std::starts_with, std::ends_with for strings
    string str = "Hello, World!";
    /*
    cout << boolalpha;
    cout << "starts_with \"Hello\": " << str.starts_with("Hello") << endl;
    cout << "ends_with \"World!\": " << str.ends_with("World!") << endl;
    cout << "contains \", \": " << str.contains(", ") << endl;
    */
    
    cout << "Note: Some C++23 algorithms not available in this compiler" << endl;
}

// 10. Monadic Operations for Optional and Expected
void demonstrateMonadicOperations() {
    cout << "\n=== Monadic Operations ===" << endl;
    
    /*
    optional<int> opt1 = 42;
    optional<int> opt2 = nullopt;
    
    // and_then: chain operations that return optional
    auto result1 = opt1.and_then([](int n) -> optional<string> {
        if (n > 0) return to_string(n);
        return nullopt;
    });
    
    // transform: apply function to value if present
    auto result2 = opt1.transform([](int n) { return n * 2; });
    
    // or_else: provide alternative if empty
    auto result3 = opt2.or_else([] { return optional<int>{100}; });
    
    cout << "opt1 and_then: " << result1.value_or("empty") << endl;
    cout << "opt1 transform: " << result2.value_or(0) << endl;
    cout << "opt2 or_else: " << result3.value_or(0) << endl;
    */
    
    cout << "Note: Monadic operations not available in this compiler" << endl;
}

int main() {
    cout << "=== C++23 FEATURES DEMONSTRATION ===" << endl << endl;
    
    // demonstrateExpected();
    demonstratePrint();
    // demonstrateDeducingThis();
    // demonstrateStacktrace();
    // demonstrateMdspan();
    // demonstrateFlatContainers();
    // demonstrateGenerator();
    demonstrateByteswap();
    demonstrateNewAlgorithms();
    demonstrateMonadicOperations();
    
    cout << "\n=== END OF C++23 DEMONSTRATION ===" << endl;
    cout << "Note: Many C++23 features require latest compiler versions" << endl;
    
    return 0;
}
C++23 Implementation Status:
  • Many C++23 features are still being implemented in compilers
  • Check compiler documentation for feature support
  • Use feature test macros to check availability
  • Some features may require specific compiler flags
  • Consider using the latest stable compiler version
  • Production code should verify feature availability

6. Smart Pointers: Modern Memory Management

Smart pointers automate memory management, preventing memory leaks and making code exception-safe. Modern C++ provides three main smart pointers.

std::unique_ptr

Exclusive ownership. Cannot be copied, only moved.

auto ptr = make_unique<T>(args);
auto moved = std::move(ptr);
// ptr is now nullptr

// Custom deleter
auto ptr = unique_ptr<FILE, 
    decltype(&fclose)>(fopen(...), fclose);
std::shared_ptr

Shared ownership. Reference counted.

auto ptr = make_shared<T>(args);
auto copy = ptr; // Ref count: 2
copy.reset();    // Ref count: 1

// Control block stores ref count
// Atomic operations for thread safety
std::weak_ptr

Non-owning reference to shared_ptr.

weak_ptr<T> weak = shared;
if (auto locked = weak.lock()) {
    // Use locked shared_ptr
} else {
    // Object was destroyed
}
Smart Pointer Best Practices
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <functional>

using namespace std;

// 1. Factory function returning unique_ptr
class Product {
    string name;
    double price;
public:
    Product(string n, double p) : name(n), price(p) {}
    void display() const {
        cout << name << ": $" << price << endl;
    }
};

unique_ptr<Product> createProduct(string name, double price) {
    return make_unique<Product>(name, price);
}

// 2. Polymorphic hierarchy with smart pointers
class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    void draw() const override {
        cout << "Drawing circle with radius " << radius << endl;
    }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    void draw() const override {
        cout << "Drawing rectangle " << width << "x" << height << endl;
    }
};

// 3. Observer pattern with weak_ptr to avoid dangling references
class Observer {
public:
    virtual void update() = 0;
    virtual ~Observer() = default;
};

class Subject {
    vector<weak_ptr<Observer>> observers;
    
    void cleanup() {
        observers.erase(
            remove_if(observers.begin(), observers.end(),
                     [](const weak_ptr<Observer>& w) {
                         return w.expired();
                     }),
            observers.end()
        );
    }
    
public:
    void addObserver(weak_ptr<Observer> observer) {
        observers.push_back(observer);
    }
    
    void notify() {
        cleanup();
        for (auto& weak : observers) {
            if (auto observer = weak.lock()) {
                observer->update();
            }
        }
    }
};

class ConcreteObserver : public Observer {
    int id;
public:
    ConcreteObserver(int i) : id(i) {}
    void update() override {
        cout << "Observer " << id << " notified!" << endl;
    }
};

// 4. Resource management with custom deleters
struct FileDeleter {
    void operator()(FILE* f) const {
        if (f) {
            fclose(f);
            cout << "File closed" << endl;
        }
    }
};

struct ArrayDeleter {
    template<typename T>
    void operator()(T* p) const {
        delete[] p;
        cout << "Array deleted" << endl;
    }
};

// 5. Enable shared_from_this for safe shared_ptr creation
class SharedObject : public enable_shared_from_this<SharedObject> {
    int data;
public:
    SharedObject(int d) : data(d) {
        cout << "SharedObject " << data << " created" << endl;
    }
    
    ~SharedObject() {
        cout << "SharedObject " << data << " destroyed" << endl;
    }
    
    shared_ptr<SharedObject> getShared() {
        return shared_from_this();
    }
    
    void process() {
        cout << "Processing " << data << endl;
    }
};

void demonstrateSmartPointers() {
    cout << "=== SMART POINTERS BEST PRACTICES ===" << endl << endl;
    
    // 1. Prefer make_unique/make_shared
    cout << "1. Factory functions with make_unique:" << endl;
    auto product = createProduct("Laptop", 999.99);
    product->display();
    
    // 2. Polymorphic containers
    cout << "\n2. Polymorphic containers:" << endl;
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Circle>(5.0));
    shapes.push_back(make_unique<Rectangle>(4.0, 6.0));
    
    for (const auto& shape : shapes) {
        shape->draw();
    }
    
    // 3. Observer pattern with weak_ptr
    cout << "\n3. Observer pattern with weak_ptr:" << endl;
    Subject subject;
    
    {
        auto obs1 = make_shared<ConcreteObserver>(1);
        auto obs2 = make_shared<ConcreteObserver>(2);
        
        subject.addObserver(obs1);
        subject.addObserver(obs2);
        
        cout << "Notifying with observers alive:" << endl;
        subject.notify();
    }
    
    cout << "\nNotifying after observers destroyed:" << endl;
    subject.notify();  // Automatically cleans up expired observers
    
    // 4. Custom deleters
    cout << "\n4. Custom deleters:" << endl;
    
    // File with custom deleter
    {
        unique_ptr<FILE, FileDeleter> file(
            fopen("test.txt", "w"), 
            FileDeleter{}
        );
        if (file) {
            fprintf(file.get(), "Test data");
            cout << "File written" << endl;
        }
    } // File automatically closed
    
    // Array with custom deleter
    {
        unique_ptr<int[], ArrayDeleter> arr(new int[10]);
        for (int i = 0; i < 10; ++i) {
            arr[i] = i * 10;
        }
        cout << "Array created and filled" << endl;
    } // Array automatically deleted
    
    // 5. enable_shared_from_this
    cout << "\n5. enable_shared_from_this:" << endl;
    {
        auto obj = make_shared<SharedObject>(42);
        auto anotherRef = obj->getShared();
        
        cout << "Use count: " << obj.use_count() << endl;
        
        // Can safely pass shared_from_this to callbacks
        function<void()> callback = [obj]() {
            obj->process();
        };
        callback();
    }
    
    // 6. Weak_ptr for breaking cycles
    cout << "\n6. Breaking cycles with weak_ptr:" << endl;
    
    struct Node {
        int value;
        shared_ptr<Node> next;
        weak_ptr<Node> prev;  // Use weak_ptr to break cycle
        
        Node(int v) : value(v) {
            cout << "Node " << value << " created" << endl;
        }
        
        ~Node() {
            cout << "Node " << value << " destroyed" << endl;
        }
    };
    
    {
        auto node1 = make_shared<Node>(1);
        auto node2 = make_shared<Node>(2);
        auto node3 = make_shared<Node>(3);
        
        // Create forward links
        node1->next = node2;
        node2->next = node3;
        
        // Create backward links with weak_ptr
        node2->prev = node1;
        node3->prev = node2;
        
        cout << "Nodes created with weak_ptr back links" << endl;
        
        // Traverse forward
        cout << "Forward traversal: ";
        auto current = node1;
        while (current) {
            cout << current->value << " ";
            current = current->next;
        }
        cout << endl;
        
        // Traverse backward using weak_ptr
        cout << "Backward traversal: ";
        current = node3;
        while (auto prev = current->prev.lock()) {
            cout << prev->value << " ";
            current = prev;
        }
        cout << endl;
    } // All nodes properly destroyed (no memory leak)
    
    // 7. Aliasing constructor
    cout << "\n7. Aliasing constructor:" << endl;
    {
        struct Base {
            int id;
            Base(int i) : id(i) {}
        };
        
        struct Derived : Base {
            string name;
            Derived(int i, string n) : Base(i), name(n) {}
        };
        
        auto derived = make_shared<Derived>(1, "Alice");
        
        // Create shared_ptr to base that shares ownership
        shared_ptr<Base> base(derived, static_cast<Base*>(derived.get()));
        
        cout << "Derived use count: " << derived.use_count() << endl;
        cout << "Base use count: " << base.use_count() << endl;
        cout << "Same object? " << (derived.get() == base.get()) << endl;
    }
    
    // 8. Performance considerations
    cout << "\n8. Performance considerations:" << endl;
    
    // make_shared vs shared_ptr(new T)
    {
        // make_shared: single allocation for object + control block
        auto ptr1 = make_shared<vector<int>>(1000);
        
        // shared_ptr(new T): two allocations
        auto ptr2 = shared_ptr<vector<int>>(new vector<int>(1000));
        
        cout << "make_shared: more efficient (one allocation)" << endl;
        cout << "shared_ptr(new T): less efficient (two allocations)" << endl;
    }
    
    cout << "\n=== END OF SMART POINTERS DEMONSTRATION ===" << endl;
}

int main() {
    demonstrateSmartPointers();
    return 0;
}
Smart Pointer Guidelines:
  • Use unique_ptr for exclusive ownership (default choice)
  • Use shared_ptr only when shared ownership is truly needed
  • Use weak_ptr to break reference cycles and for caching
  • Prefer make_unique and make_shared over explicit new
  • Never mix raw pointers and smart pointers for the same resource
  • Use enable_shared_from_this when objects need to create shared_ptr to themselves
  • Consider custom deleters for non-memory resources

7. Modern C++ Best Practices

Modern C++ Best Practices
  • Use auto for local variables when type is obvious
  • Prefer range-based for loops over iterator-based loops
  • Use smart pointers instead of raw pointers
  • Use nullptr instead of NULL or 0
  • Use constexpr for compile-time computations
  • Use noexcept for functions that don't throw
  • Use move semantics for efficient resource transfers
Common Modern C++ Mistakes
  • Using auto when type isn't clear from context
  • Capturing everything by reference in lambdas
  • Using shared_ptr when unique_ptr would suffice
  • Not marking move constructors as noexcept
  • Forgetting to implement move operations
  • Using old C headers instead of C++ equivalents
  • Not using structured bindings when appropriate
Modern vs Traditional C++ Comparison
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include <map>

using namespace std;

// Traditional C++ (pre-C++11)
class TraditionalClass {
private:
    int* data;
    size_t size;
    
public:
    // Manual memory management
    TraditionalClass(size_t s) : size(s), data(new int[s]) {
        // Initialize
    }
    
    // Rule of Three: copy constructor, copy assignment, destructor
    TraditionalClass(const TraditionalClass& other) 
        : size(other.size), data(new int[other.size]) {
        copy(other.data, other.data + size, data);
    }
    
    TraditionalClass& operator=(const TraditionalClass& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            copy(other.data, other.data + size, data);
        }
        return *this;
    }
    
    ~TraditionalClass() {
        delete[] data;
    }
    
    // No move semantics
    // Manual iteration
    void print() const {
        for (size_t i = 0; i < size; ++i) {
            cout << data[i] << " ";
        }
        cout << endl;
    }
};

// Modern C++ (C++11 and later)
class ModernClass {
private:
    vector<int> data;  // Use standard containers
    
public:
    // Use initializer lists
    ModernClass(initializer_list<int> init) : data(init) {}
    
    // Rule of Zero: compiler generates everything
    // No need to implement copy/move/destructor
    
    // Use range-based for
    void print() const {
        for (const auto& value : data) {
            cout << value << " ";
        }
        cout << endl;
    }
    
    // Use algorithms
    int sum() const {
        return accumulate(data.begin(), data.end(), 0);
    }
};

void demonstrateComparison() {
    cout << "=== MODERN VS TRADITIONAL C++ ===" << endl << endl;
    
    // 1. Variable declaration
    cout << "1. Variable Declaration:" << endl;
    
    // Traditional
    int x = 42;
    vector<int>::iterator it;
    
    // Modern
    auto y = 42;                     // auto deduction
    auto name = string("Modern");    // auto with constructor
    auto numbers = vector{1, 2, 3};  // CTAD (C++17)
    
    cout << "Traditional: explicit types" << endl;
    cout << "Modern: auto where clear" << endl;
    
    // 2. Loop iteration
    cout << "\n2. Loop Iteration:" << endl;
    
    vector<int> vec{1, 2, 3, 4, 5};
    
    cout << "Traditional:" << endl;
    for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    cout << "Modern:" << endl;
    for (const auto& value : vec) {
        cout << value << " ";
    }
    cout << endl;
    
    // 3. Function return
    cout << "\n3. Function Return:" << endl;
    
    // Traditional
    pair<int, string> makePairTraditional() {
        return pair<int, string>(42, "answer");
    }
    
    // Modern
    auto makePairModern() {
        return make_pair(42, "answer");  // C++11
        // or: return pair(42, "answer");  // C++17 CTAD
    }
    
    // Even better with structured binding
    auto [num, str] = makePairModern();
    cout << "Structured binding: " << num << ", " << str << endl;
    
    // 4. Smart pointers vs raw pointers
    cout << "\n4. Memory Management:" << endl;
    
    // Traditional (dangerous)
    int* rawPtr = new int(42);
    // ... use rawPtr ...
    delete rawPtr;  // Must remember!
    
    // Modern (safe)
    auto smartPtr = make_unique<int>(42);
    // No need to delete - automatically managed
    
    // 5. String handling
    cout << "\n5. String Handling:" << endl;
    
    // Traditional
    char oldStr[] = "Hello";
    string cppStr = "World";
    
    // Modern
    string_view view = "Modern View"sv;  // C++17
    u8string utf8 = u8"UTF-8 String";   // C++20 char8_t
    
    // 6. Map iteration
    cout << "\n6. Map Iteration:" << endl;
    
    map<int, string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    
    cout << "Traditional:" << endl;
    for (map<int, string>::const_iterator it = m.begin(); it != m.end(); ++it) {
        cout << it->first << ": " << it->second << endl;
    }
    
    cout << "Modern (C++11):" << endl;
    for (const auto& pair : m) {
        cout << pair.first << ": " << pair.second << endl;
    }
    
    cout << "Modern (C++17):" << endl;
    for (const auto& [key, value] : m) {
        cout << key << ": " << value << endl;
    }
    
    // 7. Algorithm usage
    cout << "\n7. Algorithm Usage:" << endl;
    
    vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // Traditional
    vector<int> evenTraditional;
    for (size_t i = 0; i < data.size(); ++i) {
        if (data[i] % 2 == 0) {
            evenTraditional.push_back(data[i]);
        }
    }
    
    // Modern (C++11)
    vector<int> evenModern;
    copy_if(data.begin(), data.end(), back_inserter(evenModern),
            [](int n) { return n % 2 == 0; });
    
    // Modern (C++20)
    auto evenRanges = data | views::filter([](int n) { return n % 2 == 0; });
    
    cout << "Traditional: manual loop" << endl;
    cout << "Modern (C++11): copy_if with lambda" << endl;
    cout << "Modern (C++20): ranges pipeline" << endl;
    
    // 8. Error handling
    cout << "\n8. Error Handling:" << endl;
    
    // Traditional
    int* allocateTraditional(size_t size) {
        if (size == 0) return nullptr;  // Sentinel value
        return new int[size];
    }
    
    // Modern (C++11)
    unique_ptr<int[]> allocateModern(size_t size) {
        if (size == 0) return nullptr;  // Still nullptr, but wrapped
        return make_unique<int[]>(size);
    }
    
    // Modern (C++17)
    /*
    expected<unique_ptr<int[]>, string> allocateExpected(size_t size) {
        if (size == 0) {
            return unexpected{"Size cannot be zero"};
        }
        return make_unique<int[]>(size);
    }
    */
    
    cout << "Traditional: nullptr sentinel" << endl;
    cout << "Modern (C++11): smart pointer with nullptr" << endl;
    cout << "Modern (C++17): std::expected for rich error information" << endl;
    
    // 9. Class design
    cout << "\n9. Class Design:" << endl;
    
    // Traditional: Rule of Three
    class RuleOfThree {
        int* data;
    public:
        RuleOfThree(int size) : data(new int[size]) {}
        ~RuleOfThree() { delete[] data; }
        RuleOfThree(const RuleOfThree& other);  // Copy constructor
        RuleOfThree& operator=(const RuleOfThree& other);  // Copy assignment
        // No move operations
    };
    
    // Modern: Rule of Zero or Rule of Five
    class RuleOfZero {
        vector<int> data;  // Resource managed by member
    public:
        RuleOfZero(initializer_list<int> init) : data(init) {}
        // Compiler generates everything correctly
    };
    
    class RuleOfFive {
        unique_ptr<int[]> data;
        size_t size;
    public:
        RuleOfFive(size_t s) : size(s), data(make_unique<int[]>(s)) {}
        ~RuleOfFive() = default;
        
        // Copy operations (if needed)
        RuleOfFive(const RuleOfFive& other) 
            : size(other.size), data(make_unique<int[]>(other.size)) {
            copy(other.data.get(), other.data.get() + size, data.get());
        }
        
        RuleOfFive& operator=(const RuleOfFive& other) {
            if (this != &other) {
                auto temp = make_unique<int[]>(other.size);
                copy(other.data.get(), other.data.get() + other.size, temp.get());
                data = move(temp);
                size = other.size;
            }
            return *this;
        }
        
        // Move operations (automatically noexcept)
        RuleOfFive(RuleOfFive&& other) noexcept = default;
        RuleOfFive& operator=(RuleOfFive&& other) noexcept = default;
    };
    
    cout << "Traditional: Rule of Three (copy operations + destructor)" << endl;
    cout << "Modern: Rule of Zero (let compiler handle everything)" << endl;
    cout << "Modern: Rule of Five (add move operations)" << endl;
    
    cout << "\n=== KEY TAKEAWAYS ===" << endl;
    cout << "1. Modern C++ is safer (smart pointers, range-based for)" << endl;
    cout << "2. Modern C++ is more expressive (auto, lambdas, structured bindings)" << endl;
    cout << "3. Modern C++ is more efficient (move semantics, constexpr)" << endl;
    cout << "4. Modern C++ reduces boilerplate (auto, range-based for, algorithms)" << endl;
    cout << "5. Modern C++ enables new paradigms (ranges, coroutines, concepts)" << endl;
}

int main() {
    demonstrateComparison();
    return 0;
}