Modern C++ Features with Practical Code Examples: C++98 to C++20

DISCLAIMER

This post was mostly generated by AI. Below are the prompts that were used:

Click to expand
You are a prompt engineer. Please review the prompt "You are an expert C++ developer, and you will write small C++ code snippets to showcase the most popular and common basic and advanced features of C++. Include comments explaining what each feature does and specify the version of C++ required for each feature. Do not include features from versions later than C++23. Assume that the audience is a developer with experience in a higher-level programming language." for LLM model. Enhance the prompt with enriched text for better results.
You are an expert C++ developer. Your task is to write a series of small, focused C++ code snippets that demonstrate both basic and advanced features of the language. Focus on the most popular and commonly used features, avoiding those that are less common or niche. Each snippet should:
Demonstrate a single feature or concept.
Include comments that briefly explain what the code does, highlight any C++-specific aspects, and specify the minimum C++ standard required for that feature (e.g., C++11, C++17, etc.).
Be as self-contained as possible, including necessary includes and any required setup code.
Assume the audience is a developer experienced in a higher-level programming language (such as Python, Javascript, or Ruby). Therefore:
For basic features, highlight how they differ from or are similar to those in higher-level languages.
For advanced features, explain their purpose and benefits in C++.
Organize the snippets in a logical order, starting with basic concepts (e.g., variables, control structures, functions, classes) and progressing to more advanced topics (e.g., templates, STL, lambdas, smart pointers, concurrency).
Ensure that each snippet is correct and would compile with the specified C++ standard. Use modern C++ practices where appropriate (e.g., prefer std::string over C-style strings, use smart pointers instead of raw pointers).
Do not include features introduced after C++23.

Basic Features

1. Variables and Basic Data Types

// #include is not the same as import in other languages.
// #include essentially copies and pastes the code from the included file.
// <> looks for files in the compiler's system directories.
// "" looks for files within your project.
#include <iostream>
#include <string>
// #include "Something.h"

// Minimum C++ standard: C++98
int main() {
    // Basic variable declaration and initialization
    int number = 42;                // Integer, 32 bit
    double pi = 3.141592653589793;  // Floating point, 64 bit
    bool isValid = true;            // Boolean
    char letter = 'A';              // Character, 8 bit
    std::string text = "Hello";     // String (from standard library)

    // 'unsigned' means a variable can only store non-negative values
    // "'" is a digit separator (C++14)
    unsigned float number2 = 3.14159f; // 32 bit, the 'f' is necessary to specify a float literal
    short number2(static_cast<short>(32'767)); // 16 bit, direct initialization, slightly more efficient
    long number3{9'223'372'036'854'775'807}; // 32/64 bit, list initialization, prevents narrowing (C++11),

    // C++ is statically typed, unlike Python/JavaScript which are dynamically typed
    // Type must be declared and cannot change during runtime

    // Modern C++ (C++11) allows type inference with auto
    auto inferred_number = 100;     // Type is inferred as int
    auto inferred_text = "World";   // Type is inferred as const char*

    // Output
    std::cout << "Number: " << number << std::endl;  // Output: Number: 42
    std::cout << "Text: " << text << std::endl;  // Output: Text: Hello

    return 0;
}

2. Control Flow Structures

#include <iostream>
#include <vector>

// Minimum C++ standard: C++98
int main() {
    // If-else statement
    int x = 10;
    if (x > 5) {
        std::cout << "x is greater than 5" << std::endl;  // Output: x is greater than 5
    } else if (x == 5) {
        std::cout << "x equals 5" << std::endl;
    } else {
        std::cout << "x is less than 5" << std::endl;
    }

    // Switch statement
    char grade = 'B';
    switch (grade) {
        case 'A':
            std::cout << "Excellent" << std::endl;
            break;
        case 'B':
            std::cout << "Good" << std::endl;  // Output: Good
            break;
        default:
            std::cout << "Other grade" << std::endl;
    }

    // For loop
    for (int i = 0; i < 3; i++) {
        std::cout << "For loop iteration: " << i << std::endl;
        // Iteration 1: For loop iteration: 0
        // Iteration 2: For loop iteration: 1
        // Iteration 3: For loop iteration: 2
    }

    // Range-based for loop (C++11)
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (const auto& num : numbers) {
        std::cout << "Number: " << num << std::endl;
        // Iteration 1: Number: 1
        // Iteration 2: Number: 2
        // Iteration 3: Number: 3
        // Iteration 4: Number: 4
        // Iteration 5: Number: 5
    }

    // While loop
    int count = 0;
    while (count < 3) {
        std::cout << "While loop count: " << count << std::endl;
        // Iteration 1: While loop count: 0
        // Iteration 2: While loop count: 1
        // Iteration 3: While loop count: 2
        count++;
    }

    // Do-while loop (executes at least once)
    int y = 0;
    do {
        std::cout << "Do-while loop: " << y << std::endl;
        // Iteration 1: Do-while loop: 0
        // Iteration 2: Do-while loop: 1
        // Iteration 3: Do-while loop: 2
        y++;
    } while (y < 3);

    return 0;
}

3. Functions

#include <iostream>
#include <string>

// Minimum C++ standard: C++98/C++11

// Basic function declaration
int add(int a, int b) {
    return a + b;
}

// Function with default parameters
std::string greet(const std::string& name = "World") {
    return "Hello, " + name + "!";
}

// Pass by value - creates a copy of the argument
void incrementByValue(int num) {
    num++;  // Only affects the local copy, not the original
}

// Pass by reference - operates on the original variable
void incrementByReference(int& num) {
    num++;  // Modifies the original variable
}

// Pass by const reference - efficient but prevents modification
void printValue(const int& num) {
    // num++; // Would cause compilation error
    std::cout << "Value: " << num << std::endl;
}

// Function overloading (same name, different parameters)
double multiply(double a, double b) {
    return a * b;
}

int multiply(int a, int b, int c) {
    return a * b * c;
}

// C++11: Function with auto return type deduction
auto subtract(int a, int b) -> int {  // C++11 trailing return type
    return a - b;
}

int main() {
    std::cout << "5 + 3 = " << add(5, 3) << std::endl;  // Output: 5 + 3 = 8
    std::cout << greet() << std::endl;  // Output: Hello, World!
    std::cout << greet("C++") << std::endl;  // Output: Hello, C++!

    int x = 5;
    incrementByValue(x);
    std::cout << "After incrementByValue: " << x << std::endl;  // Still 5

    incrementByReference(x);
    std::cout << "After incrementByReference: " << x << std::endl;  // Now 6

    std::cout << "2.5 * 3.0 = " << multiply(2.5, 3.0) << std::endl;  // Output: 2.5 * 3.0 = 7.5
    std::cout << "2 * 3 * 4 = " << multiply(2, 3, 4) << std::endl;  // Output: 2 * 3 * 4 = 24

    std::cout << "10 - 7 = " << subtract(10, 7) << std::endl;  // Output: 10 - 7 = 3

    return 0;
}

4. Basic Classes and Objects

#include <iostream>
#include <string>

// Minimum C++ standard: C++98

// A simple class definition
class Person {
private:  // Access specifier - these members are only accessible within the class
    std::string name;
    int age;

public:   // These members are accessible from outside the class
    // Constructor
    Person(const std::string& name, int age) {
        this->name = name;  // 'this' pointer refers to the current instance
        this->age = age;
    }
    // Constructor using member initializer list
    // Person(const std::string& name, int age) : name(name), age(age) {}

    // Destructor - called when the object goes out of scope
    ~Person() {
        std::cout << name << " is being destroyed." << std::endl;
    }

    // Member functions (methods)
    void introduce() {
        std::cout << "Hi, I'm " << name << " and I'm " << age << " years old." << std::endl;
    }

    // Getters and setters
    std::string getName() const {  // 'const' means this method won't modify the object
        return name;
    }

    void setName(const std::string& newName) {
        name = newName;
    }

    int getAge() const {
        return age;
    }

    void setAge(int newAge) {
        if (newAge > 0) {  // Basic validation
            age = newAge;
        }
    }
};

int main() {
    // Creating an object (instance) of the class
    Person alice("Alice", 25);
    alice.introduce();  // Output: Hi, I'm Alice and I'm 25 years old.

    // Accessing and modifying object properties via methods
    alice.setName("Alice Smith");
    std::cout << "Name: " << alice.getName() << std::endl; // Output: Name: Alice Smith

    // Create another person
    Person bob("Bob", 30);
    bob.introduce();  // Output: Hi, I'm Bob and I'm 30 years old.

    return 0;
}

Intermediate Features

5. Class Inheritance and Polymorphism

#include <iostream>
#include <string>
#include <vector>

// Minimum C++ standard: C++98

// Base class
class Shape {
protected:  // Accessible to derived classes but not outside the hierarchy
    std::string color;

public:
    Shape(const std::string& color) : color(color) {}

    // Virtual function - can be overridden by derived classes
    virtual double area() const {
        return 0.0;  // Default implementation
    }

    // Pure virtual function - must be implemented by derived classes
    virtual void draw() const = 0;  // Makes Shape an abstract class

    // Virtual destructor - essential for proper cleanup in polymorphic classes
    virtual ~Shape() {
        std::cout << "Shape destructor called" << std::endl;
    }

    std::string getColor() const {
        return color;
    }
};

// Derived class
class Circle : public Shape {  // Public inheritance
private:
    double radius;

public:
    Circle(const std::string& color, double radius)
        : Shape(color), radius(radius) {}  // Initialize base class first

    // Override area() method
    double area() const override {  // 'override' keyword is C++11
        return 3.14159 * radius * radius;
    }

    // Implement the pure virtual function
    void draw() const override {
        std::cout << "Drawing a " << color << " circle with radius " << radius << std::endl;
    }

    // Derived class destructor
    ~Circle() override {
        std::cout << "Circle destructor called" << std::endl;
    }
};

// Another derived class
class Rectangle : public Shape {
private:
    double width;
    double height;

public:
    Rectangle(const std::string& color, double width, double height)
        : Shape(color), width(width), height(height) {}

    double area() const override {
        return width * height;
    }

    void draw() const override {
        std::cout << "Drawing a " << color << " rectangle with dimensions "
                  << width << "x" << height << std::endl;
    }

    ~Rectangle() override {
        std::cout << "Rectangle destructor called" << std::endl;
    }
};

int main() {
    // Shape shape("Red");  // Error: Cannot instantiate abstract class

    Circle circle("Blue", 5.0);
    Rectangle rectangle("Green", 4.0, 6.0);

    circle.draw();  // Output: Drawing a Blue circle with radius 5
    rectangle.draw();  // Output: Drawing a Green rectangle with dimensions 4x6

    std::cout << "Circle area: " << circle.area() << std::endl;  // Output: Circle area: 78.5397
    std::cout << "Rectangle area: " << rectangle.area() << std::endl;  // Output: Rectangle area: 24

    // Polymorphism - treat different derived types as base type
    std::vector<Shape*> shapes;
    shapes.push_back(&circle);
    shapes.push_back(&rectangle);

    // Call methods through base class pointer
    for (const Shape* shape : shapes) {
        shape->draw();
        // Iteration 1: Drawing a Blue circle with radius 5
        // Iteration 2: Drawing a Green rectangle with dimensions 4x6

        std::cout << "Area: " << shape->area() << std::endl;
        // Iteration 1: Area: 78.5397
        // Iteration 2: Area: 24
    }

    return 0;
}

6. Templates

#include <iostream>
#include <string>

// Minimum C++ standard: C++98/C++11

// Function template
template<typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Class template
template<typename T>
class Box {
private:
    T content;

public:
    Box(const T& item) : content(item) {}

    T getContent() const {
        return content;
    }

    void setContent(const T& item) {
        content = item;
    }

    void describe() const {
        std::cout << "Box contains: " << content << std::endl;
    }
};

// Template with multiple type parameters
template<typename K, typename V>
class Pair {
private:
    K key;
    V value;

public:
    Pair(const K& k, const V& v) : key(k), value(v) {}

    K getKey() const { return key; }
    V getValue() const { return value; }

    void display() const {
        std::cout << key << " -> " << value << std::endl;
    }
};

// Template specialization (C++98)
template<>
class Box<bool> {
private:
    bool content;

public:
    Box(bool item) : content(item) {}

    bool getContent() const {
        return content;
    }

    void describe() const {
        std::cout << "Boolean box contains: " << (content ? "true" : "false") << std::endl;
    }
};

int main() {
    // Using function template
    std::cout << "Max of 5 and 10: " << maximum(5, 10) << std::endl;
    std::cout << "Max of 3.14 and 2.71: " << maximum(3.14, 2.71) << std::endl;
    std::cout << "Max of 'a' and 'z': " << maximum('a', 'z') << std::endl;
    std::cout << "Max of \"apple\" and \"banana\": " << maximum(std::string("apple"), std::string("banana")) << std::endl;

    // Using class template
    Box<int> intBox(123);
    intBox.describe();

    Box<std::string> stringBox("Hello Templates");
    stringBox.describe();

    // Using template specialization
    Box<bool> boolBox(true);
    boolBox.describe();

    // Using multiple type parameters
    Pair<int, std::string> student(42, "John");
    student.display();

    return 0;
}

7. STL Containers

#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <algorithm>
#include <string>

// Minimum C++ standard: C++11

int main() {
    // Vector - dynamic array
    std::vector<int> numbers = {10, 20, 30, 40, 50};

    std::cout << "Vector elements: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // Add and remove elements
    numbers.push_back(60);  // Add to end
    numbers.pop_back();     // Remove from end

    // Vector size and element access
    std::cout << "Vector size: " << numbers.size() << std::endl;
    std::cout << "First element: " << numbers.front() << std::endl;
    std::cout << "Last element: " << numbers.back() << std::endl;
    std::cout << "Element at index 2: " << numbers[2] << std::endl;  // No bounds checking
    std::cout << "Element at index 2 (safe): " << numbers.at(2) << std::endl;  // With bounds checking

    // List - doubly linked list
    std::list<std::string> fruits = {"apple", "banana", "orange"};

    fruits.push_front("grape");  // Add to front (O(1) - unlike vector)
    fruits.push_back("kiwi");    // Add to back

    std::cout << "List elements: ";
    for (const auto& fruit : fruits) {
        std::cout << fruit << " ";
    }
    std::cout << std::endl;

    // Map - key-value pairs (implemented as Red-Black Tree)
    std::map<std::string, int> ages;
    ages["Alice"] = 30;
    ages["Bob"] = 25;
    ages["Charlie"] = 35;

    std::cout << "Map entries:" << std::endl;
    for (const auto& pair : ages) {  // C++11 auto and range-based for
        std::cout << pair.first << " is " << pair.second << " years old" << std::endl;
    }

    // Check if key exists
    std::string name = "David";
    if (ages.find(name) != ages.end()) {
        std::cout << name << "'s age: " << ages[name] << std::endl;
    } else {
        std::cout << name << " not found in the map" << std::endl;
    }

    // Set - unique elements, sorted
    std::set<int> uniqueNumbers = {30, 20, 10, 30, 40, 10};

    std::cout << "Set elements (duplicates removed, sorted): ";
    for (int num : uniqueNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // Insert element
    uniqueNumbers.insert(25);

    // Check if element exists
    int searchValue = 20;
    if (uniqueNumbers.count(searchValue) > 0) {
        std::cout << searchValue << " found in the set" << std::endl;
    }

    return 0;
}

8. STL Algorithms

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>

// Minimum C++ standard: C++11

// Helper function to print a container
template<typename Container>
void print(const Container& c, const std::string& label) {
    std::cout << label << ": ";
    for (const auto& item : c) {
        std::cout << item << " ";
    }
    std::cout << std::endl;
}

int main() {
    // Initialize a vector
    std::vector<int> numbers = {5, 2, 9, 1, 7, 3, 8, 6, 4};
    print(numbers, "Original vector");

    // Sorting algorithms
    std::sort(numbers.begin(), numbers.end());  // Sort ascending
    print(numbers, "Sorted vector");

    std::sort(numbers.begin(), numbers.end(), std::greater<int>());  // Sort descending
    print(numbers, "Reverse sorted vector");

    // Find algorithms
    auto it = std::find(numbers.begin(), numbers.end(), 7);
    if (it != numbers.end()) {
        std::cout << "Found 7 at position: " << (it - numbers.begin()) << std::endl;
    }

    // Minimum and maximum
    auto [minIt, maxIt] = std::minmax_element(numbers.begin(), numbers.end());
    std::cout << "Min: " << *minIt << ", Max: " << *maxIt << std::endl;

    // Counting
    int count = std::count_if(numbers.begin(), numbers.end(),
                             [](int n) { return n % 2 == 0; });  // Count even numbers
    std::cout << "Number of even elements: " << count << std::endl;

    // Transformations
    std::vector<int> squared(numbers.size());
    std::transform(numbers.begin(), numbers.end(), squared.begin(),
                  [](int n) { return n * n; });  // Square each element
    print(squared, "Squared values");

    // Numerical algorithms
    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
    std::cout << "Sum of elements: " << sum << std::endl;

    // Rearranging elements
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::random_shuffle(nums.begin(), nums.end());  // Random shuffle
    print(nums, "Shuffled vector");

    // Partitioning
    auto partitionPoint = std::partition(nums.begin(), nums.end(),
                                        [](int n) { return n % 2 == 0; });  // Move even numbers to front
    print(nums, "Partitioned vector (evens first)");

    // Generate sequence
    std::vector<int> sequence(10);
    std::iota(sequence.begin(), sequence.end(), 1);  // Fill with 1, 2, 3, ...
    print(sequence, "Generated sequence");

    return 0;
}

Advanced Features

9. Lambda Expressions

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

// Minimum C++ standard: C++11/C++14

void demonstrate_lambdas() {
    // Basic lambda expression
    auto greet = []() {
        std::cout << "Hello from lambda!" << std::endl;
    };

    // Call the lambda
    greet();

    // Lambda with parameters
    auto add = [](int a, int b) {
        return a + b;
    };

    std::cout << "5 + 3 = " << add(5, 3) << std::endl;

    // Lambda capturing variables by value
    int x = 10;
    auto increment_by_x = [x](int value) {
        return value + x;
    };

    std::cout << "15 + x = " << increment_by_x(15) << std::endl;

    // Lambda capturing variables by reference
    auto increment_and_modify_x = [&x](int value) {
        x += value;  // This modifies the original x
        return x;
    };

    std::cout << "After adding 5: " << increment_and_modify_x(5) << std::endl;
    std::cout << "New value of x: " << x << std::endl;

    // Capture all variables by value [=] or by reference [&]
    int y = 20;
    auto capture_all_by_value = [=]() {
        return x + y;  // Uses copies of x and y
    };

    auto capture_all_by_reference = [&]() {
        x += 1;  // Modifies original x
        y += 1;  // Modifies original y
        return x + y;
    };

    std::cout << "Capture by value result: " << capture_all_by_value() << std::endl;
    std::cout << "Capture by reference result: " << capture_all_by_reference() << std::endl;
    std::cout << "After reference capture: x = " << x << ", y = " << y << std::endl;

    // Lambda with explicit return type
    auto divide = [](double a, double b) -> double {
        if (b == 0) return 0;  // Avoid division by zero
        return a / b;
    };

    std::cout << "10 / 2 = " << divide(10, 2) << std::endl;

    // Using lambda with algorithms
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Filter even numbers
    std::vector<int> even_numbers;
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(even_numbers),
                [](int n) { return n % 2 == 0; });

    std::cout << "Even numbers: ";
    for (int num : even_numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // C++14: Generic lambdas with auto parameters
    auto generic_add = [](auto a, auto b) {
        return a + b;
    };

    std::cout << "Integer add: " << generic_add(5, 3) << std::endl;
    std::cout << "Double add: " << generic_add(3.14, 2.71) << std::endl;
    std::cout << "String add: " << generic_add(std::string("Hello, "), std::string("World!")) << std::endl;
}

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

10. Smart Pointers

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// Minimum C++ standard: C++11

// A simple class to demonstrate ownership
class Resource {
private:
    std::string name;

public:
    Resource(const std::string& n) : name(n) {
        std::cout << "Resource " << name << " created" << std::endl;
    }

    ~Resource() {
        std::cout << "Resource " << name << " destroyed" << std::endl;
    }

    void use() const {
        std::cout << "Using resource " << name << std::endl;
    }
};

void demonstrate_smart_pointers() {
    // 1. unique_ptr - exclusive ownership
    {
        std::cout << "\n--- unique_ptr example ---\n";

        // Create a unique_ptr
        std::unique_ptr<Resource> res1 = std::make_unique<Resource>("First");  // C++14
        // In C++11, use: std::unique_ptr<Resource> res1(new Resource("First"));

        // Use the resource
        res1->use();

        // Transfer ownership (move)
        std::unique_ptr<Resource> res2 = std::move(res1);

        // res1 is now null
        if (!res1) {
            std::cout << "res1 is now empty" << std::endl;
        }

        // res2 now owns the resource
        res2->use();

        // Cannot copy unique_ptr (would not compile)
        // std::unique_ptr<Resource> res3 = res2;  // Error!

        // Automatically destroyed at end of scope
    }

    // 2. shared_ptr - shared ownership
    {
        std::cout << "\n--- shared_ptr example ---\n";

        // Create a shared_ptr
        std::shared_ptr<Resource> res1 = std::make_shared<Resource>("Second");

        {
            // Create another shared_ptr pointing to the same resource
            std::shared_ptr<Resource> res2 = res1;

            // Both can use the resource
            res1->use();
            res2->use();

            std::cout << "Reference count: " << res1.use_count() << std::endl;

            // res2 goes out of scope here, but resource isn't destroyed yet
        }

        std::cout << "After inner scope, reference count: " << res1.use_count() << std::endl;

        // Resource still accessible through res1
        res1->use();

        // Will be destroyed when res1 goes out of scope
    }

    // 3. weak_ptr - non-owning reference to a shared_ptr
    {
        std::cout << "\n--- weak_ptr example ---\n";

        // Create a shared_ptr in a separate scope
        std::shared_ptr<Resource> sharedRes;
        std::weak_ptr<Resource> weakRes;

        {
            auto tempRes = std::make_shared<Resource>("Third");
            weakRes = tempRes;     // weakRes doesn't increase reference count
            sharedRes = tempRes;   // sharedRes does increase reference count

            std::cout << "Inside scope, is weak_ptr expired? "
                      << weakRes.expired() << std::endl;

            if (auto lockedPtr = weakRes.lock()) {  // Convert to shared_ptr if still alive
                lockedPtr->use();
            }
        }

        // tempRes is gone, but sharedRes keeps the resource alive
        std::cout << "After scope, is weak_ptr expired? "
                  << weakRes.expired() << std::endl;

        // Release the shared_ptr
        sharedRes.reset();

        // Now the resource is definitely gone
        std::cout << "After reset, is weak_ptr expired? "
                  << weakRes.expired() << std::endl;

        // Safe way to use weak_ptr
        if (auto lockedPtr = weakRes.lock()) {  // Will return empty shared_ptr
            lockedPtr->use();
        } else {
            std::cout << "Resource is no longer available" << std::endl;
        }
    }

    // Example of a container of smart pointers
    {
        std::cout << "\n--- Container of smart pointers ---\n";

        std::vector<std::shared_ptr<Resource>> resources;

        // Add resources to the container
        resources.push_back(std::make_shared<Resource>("Container1"));
        resources.push_back(std::make_shared<Resource>("Container2"));
        resources.push_back(std::make_shared<Resource>("Container3"));

        // Use the resources
        for (const auto& res : resources) {
            res->use();
        }

        // All resources will be automatically cleaned up when the vector is destroyed
    }
}

int main() {
    demonstrate_smart_pointers();

    // No manual memory management needed!
    return 0;
}

11. Move Semantics and Perfect Forwarding

#include <iostream>
#include <vector>
#include <string>
#include <utility>

// Minimum C++ standard: C++11

// A class that demonstrates move semantics
class DataContainer {
private:
    int* data;
    size_t size;

public:
    // Constructor
    DataContainer(size_t size) : size(size) {
        std::cout << "Allocating " << size << " integers" << std::endl;
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = static_cast<int>(i);
        }
    }

    // Destructor
    ~DataContainer() {
        std::cout << "Destroying DataContainer with " << size << " integers" << std::endl;
        delete[] data;
    }

    // Copy constructor (expensive)
    DataContainer(const DataContainer& other) : size(other.size) {
        std::cout << "Copy constructor: copying " << size << " integers" << std::endl;
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }

    // Move constructor (efficient)
    DataContainer(DataContainer&& other) noexcept : data(nullptr), size(0) {
        std::cout << "Move constructor: stealing " << other.size << " integers" << std::endl;

        // Steal resources from other
        data = other.data;
        size = other.size;

        // Leave other in a valid but empty state
        other.data = nullptr;
        other.size = 0;
    }

    // Copy assignment operator
    DataContainer& operator=(const DataContainer& other) {
        if (this != &other) {  // Prevent self-assignment
            std::cout << "Copy assignment: copying " << other.size << " integers" << std::endl;

            // Free current resources
            delete[] data;

            // Copy from other
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i) {
                data[i] = other.data[i];
            }
        }
        return *this;
    }

    // Move assignment operator
    DataContainer& operator=(DataContainer&& other) noexcept {
        if (this != &other) {  // Prevent self-assignment
            std::cout << "Move assignment: stealing " << other.size << " integers" << std::endl;

            // Free current resources
            delete[] data;

            // Steal resources from other
            data = other.data;
            size = other.size;

            // Leave other in a valid but empty state
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }

    // Utility method to show container state
    void display() const {
        if (data) {
            std::cout << "Container has " << size << " elements: [";
            for (size_t i = 0; i < std::min(size, static_cast<size_t>(5)); ++i) {
                std::cout << data[i];
                if (i < std::min(size, static_cast<size_t>(5)) - 1) {
                    std::cout << ", ";
                }
            }
            if (size > 5) std::cout << ", ...";
            std::cout << "]" << std::endl;
        } else {
            std::cout << "Container is empty" << std::endl;
        }
    }

    // Get size
    size_t getSize() const {
        return size;
    }
};

// Function template demonstrating perfect forwarding
template<typename T>
void process(T&& arg) {
    std::cout << "Processing argument" << std::endl;

    // Forward the argument with its original value category preserved
    consume(std::forward<T>(arg));
}

// Functions for demonstration
void consume(const DataContainer& container) {
    std::cout << "consume(const DataContainer&) called with size " << container.getSize() << std::endl;
}

void consume(DataContainer&& container) {
    std::cout << "consume(DataContainer&&) called with size " << container.getSize() << std::endl;
    // We could move from container here if needed
}

// Simple function to create and return a DataContainer
DataContainer createContainer(size_t size) {
    return DataContainer(size);  // Return value optimization (RVO) may apply
}

int main() {
    std::cout << "--- Move Semantics Demo ---" << std::endl;

    // Regular construction
    DataContainer c1(10);
    c1.display();

    // Copy construction - expensive copy
    std::cout << "\nCopying c1 to c2:" << std::endl;
    DataContainer c2(c1);
    c2.display();

    // Move construction - efficient "theft"
    std::cout << "\nMoving temporary to c3:" << std::endl;
    DataContainer c3(createContainer(8));
    c3.display();

    // Move assignment
    std::cout << "\nMoving temporary to c1:" << std::endl;
    c1 = createContainer(5);
    c1.display();

    // Manual move with std::move
    std::cout << "\nExplicitly moving c2 to c4:" << std::endl;
    DataContainer c4(std::move(c2));  // Explicitly cast to rvalue
    c4.display();

    // c2 should now be empty, as its resources were moved
    std::cout << "\nAfter move, c2 is now:" << std::endl;
    c2.display();

    std::cout << "\n--- Perfect Forwarding Demo ---" << std::endl;

    // Lvalue reference scenario
    DataContainer container(3);
    std::cout << "\nCalling process() with lvalue:" << std::endl;
    process(container);  // container is an lvalue

    // Rvalue reference scenario
    std::cout << "\nCalling process() with rvalue:" << std::endl;
    process(createContainer(7));  // createContainer(7) is an rvalue

    // std::move turns an lvalue into an rvalue
    std::cout << "\nCalling process() with std::move:" << std::endl;
    process(std::move(container));  // std::move(container) is an rvalue

    return 0;
}

12. Concurrency

#include <iostream>
#include <thread>
#include <mutex>
#include <future>
#include <chrono>
#include <vector>

// Minimum C++ standard: C++11/C++14

// Global mutex for synchronization
std::mutex printMutex;

// Basic function to run in a thread
void basic_thread_function(int id) {
    // Use a lock guard to safely access std::cout
    {
        std::lock_guard<std::mutex> lock(printMutex);
        std::cout << "Thread " << id << " starting" << std::endl;
    }

    // Simulate some work
    std::this_thread::sleep_for(std::chrono::milliseconds(200 * id));

    {
        std::lock_guard<std::mutex> lock(printMutex);
        std::cout << "Thread " << id << " finished" << std::endl;
    }
}

// Function that returns a value
int calculate_something(int value) {
    // Simulate complex calculation
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    return value * value;
}

// Function that uses a shared resource
void increment_counter(int& counter, std::mutex& mutex, int iterations) {
    for (int i = 0; i < iterations; ++i) {
        // Lock the mutex before modifying shared data
        std::lock_guard<std::mutex> lock(mutex);
        ++counter;
    }
}

int main() {
    std::cout << "=== Basic Threading ===" << std::endl;

    // Create and start multiple threads
    std::thread t1(basic_thread_function, 1);
    std::thread t2(basic_thread_function, 2);
    std::thread t3(basic_thread_function, 3);

    // Wait for all threads to complete
    t1.join();
    t2.join();
    t3.join();

    std::cout << "\n=== Future and Promise ===" << std::endl;

    // Using std::async to execute a task asynchronously
    std::future<int> resultFuture = std::async(std::launch::async, calculate_something, 10);

    // Do other work while calculation is running
    std::cout << "Waiting for result..." << std::endl;

    // Get the result (blocks until ready)
    int result = resultFuture.get();
    std::cout << "Result: " << result << std::endl;

    std::cout << "\n=== Sharing Data Between Threads ===" << std::endl;

    int sharedCounter = 0;
    std::mutex counterMutex;

    // Create threads that increment the shared counter
    std::thread counter1(increment_counter, std::ref(sharedCounter), std::ref(counterMutex), 1000);
    std::thread counter2(increment_counter, std::ref(sharedCounter), std::ref(counterMutex), 1000);

    counter1.join();
    counter2.join();

    std::cout << "Final counter value: " << sharedCounter << std::endl;

    std::cout << "\n=== Parallel Execution with std::async ===" << std::endl;

    // Execute multiple tasks in parallel
    auto future1 = std::async(std::launch::async, []() {
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
        return std::string("First task completed");
    });

    auto future2 = std::async(std::launch::async, []() {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        return std::string("Second task completed");
    });

    // Wait for and print results as they become available
    std::cout << future2.get() << std::endl;  // This will complete first
    std::cout << future1.get() << std::endl;  // This will complete second

    std::cout << "\n=== Condition Variables ===" << std::endl;

    // Demonstrate a producer-consumer scenario
    std::vector<int> data;
    std::mutex dataMutex;
    std::condition_variable dataCondition;
    bool dataReady = false;
    bool processingDone = false;

    // Producer thread
    std::thread producer([&]() {
        // Produce data
        {
            std::lock_guard<std::mutex> lock(dataMutex);
            std::cout << "Producer: preparing data..." << std::endl;

            // Simulate work
            std::this_thread::sleep_for(std::chrono::milliseconds(500));

            // Add items
            data.push_back(1);
            data.push_back(2);
            data.push_back(3);

            std::cout << "Producer: data ready" << std::endl;
            dataReady = true;
        }

        // Notify the consumer
        dataCondition.notify_one();
    });

    // Consumer thread
    std::thread consumer([&]() {
        // Wait for data to be ready
        std::unique_lock<std::mutex> lock(dataMutex);

        std::cout << "Consumer: waiting for data..." << std::endl;

        dataCondition.wait(lock, [&]{ return dataReady; });

        // Process data
        std::cout << "Consumer: processing data - ";
        int sum = 0;
        for (int value : data) {
            sum += value;
            std::cout << value << " ";
        }
        std::cout << std::endl;

        std::cout << "Consumer: sum = " << sum << std::endl;
        processingDone = true;
    });

    producer.join();
    consumer.join();

    return 0;
}

13. Variadic Templates

#include <iostream>
#include <string>
#include <tuple>
#include <utility>

// Minimum C++ standard: C++11/C++14

// Base case: no arguments
void print() {
    std::cout << std::endl;
}

// Recursive variadic template function
template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // Recursive call with one less argument
}

// Sum function using parameter pack
template<typename T>
T sum(T value) {
    return value;
}

template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);  // Recursively sum the remaining arguments
}

// Function to print a tuple (C++14)
template<typename Tuple, size_t... I>
void print_tuple_impl(const Tuple& t, std::index_sequence<I...>) {
    // Fold expression in C++17 would be simpler
    print(std::get<I>(t)...);
}

template<typename... Args>
void print_tuple(const std::tuple<Args...>& t) {
    print_tuple_impl(t, std::index_sequence_for<Args...>{});
}

// Perfect forwarding with variadic templates
template<typename... Args>
void forward_to_print(Args&&... args) {
    print(std::forward<Args>(args)...);  // Forward all arguments to print
}

// A variadic class template
template<typename... Types>
class TypePackInfo {
public:
    static constexpr size_t size = sizeof...(Types);

    void print_info() const {
        std::cout << "TypePackInfo contains " << size << " types." << std::endl;
    }
};

// Recursive helper for processing tuple at compile time
template<typename F, typename Tuple, size_t... I>
auto transform_tuple_impl(F&& f, const Tuple& t, std::index_sequence<I...>) {
    return std::make_tuple(f(std::get<I>(t))...);
}

template<typename F, typename... Args>
auto transform_tuple(F&& f, const std::tuple<Args...>& t) {
    return transform_tuple_impl(std::forward<F>(f), t,
                               std::index_sequence_for<Args...>{});
}

int main() {
    std::cout << "=== Variadic Function Templates ===" << std::endl;

    // Call print with different types and numbers of arguments
    print("Hello", 42, 3.14, true);

    // Call sum with multiple arguments
    int sumInt = sum(1, 2, 3, 4, 5);
    std::cout << "Sum of integers: " << sumInt << std::endl;

    double sumDouble = sum(1.1, 2.2, 3.3);
    std::cout << "Sum of doubles: " << sumDouble << std::endl;

    std::cout << "\n=== Tuples and Variadic Templates ===" << std::endl;

    // Create a tuple with variadic template arguments
    auto person = std::make_tuple("John", 30, 75.5, true);

    std::cout << "Person tuple: ";
    print_tuple(person);

    // Access tuple elements
    std::cout << "Name: " << std::get<0>(person) << std::endl;
    std::cout << "Age: " << std::get<1>(person) << std::endl;

    std::cout << "\n=== Perfect Forwarding with Variadic Templates ===" << std::endl;

    std::string name = "Alice";
    forward_to_print("Forwarded:", name, 25, 'A');

    // Demonstrate moving
    std::string temporary = "This will be moved";
    forward_to_print("Moving:", std::move(temporary), 100);
    std::cout << "After move, temporary contains: \"" << temporary << "\"" << std::endl;

    std::cout << "\n=== Variadic Class Templates ===" << std::endl;

    TypePackInfo<int, double, char> info1;
    info1.print_info();

    TypePackInfo<> empty_info;
    empty_info.print_info();

    TypePackInfo<int, std::string, bool, float, char, double> info2;
    info2.print_info();

    std::cout << "\n=== Compile-Time Tuple Transformation ===" << std::endl;

    // Create a tuple of values
    auto values = std::make_tuple(1, 2.5, std::string("Hello"));

    // Transform each element by doubling numbers and appending to strings
    auto transformed = transform_tuple([](const auto& x) -> decltype(auto) {
        if constexpr (std::is_arithmetic<std::decay_t<decltype(x)>>::value) {
            return x * 2;  // Double numbers
        } else if constexpr (std::is_same<std::decay_t<decltype(x)>, std::string>::value) {
            return x + " World";  // Append to strings
        } else {
            return x;  // Leave other types unchanged
        }
    }, values);

    std::cout << "Original tuple: ";
    print_tuple(values);

    std::cout << "Transformed tuple: ";
    print_tuple(transformed);

    return 0;
}

14. C++20 Concepts

#include <iostream>
#include <string>
#include <vector>
#include <concepts>
#include <type_traits>

// Minimum C++ standard: C++20

// Basic concept: Type must be a numeric type
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;

// Constrained function template using a concept
template<Numeric T>
T add(T a, T b) {
    return a + b;
}

// Concept that requires equality comparison
template<typename T>
concept Equality_comparable = requires(T a, T b) {
    { a == b } -> std::convertible_to<bool>;
    { a != b } -> std::convertible_to<bool>;
};

// Compound concept
template<typename T>
concept Sortable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
} && std::copyable<T>;

// Function template constrained by multiple concepts
template<Numeric T, Equality_comparable U>
void process(T value, U item) {
    std::cout << "Processing " << value << " and " << item << std::endl;
}

// A concept that constrains a return type
template<typename T>
concept ToString = requires(T a) {
    { a.to_string() } -> std::convertible_to<std::string>;
};

// A class template constrained by a concept
template<Numeric T>
class NumericContainer {
private:
    std::vector<T> values;

public:
    void add(T value) {
        values.push_back(value);
    }

    T sum() const {
        T result = T();
        for (const auto& val : values) {
            result += val;
        }
        return result;
    }

    T average() const requires std::floating_point<T> {  // Additional constraint
        if (values.empty()) return T();
        return sum() / static_cast<T>(values.size());
    }

    void display() const {
        for (const auto& val : values) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }
};

// A class that satisfies ToString
class Person {
private:
    std::string name;
    int age;

public:
    Person(const std::string& n, int a) : name(n), age(a) {}

    std::string to_string() const {
        return name + " (" + std::to_string(age) + ")";
    }
};

// Concept with auto parameters (abbreviated function template)
void print_if_numeric(const auto& value) requires Numeric<decltype(value)> {
    std::cout << "Numeric value: " << value << std::endl;
}

// Requires clause in function body
template<typename T>
void check_sortable(const T& value) {
    std::cout << "Checking if type is sortable... ";

    if constexpr (Sortable<T>) {
        std::cout << "Yes, it is sortable!" << std::endl;
    } else {
        std::cout << "No, it is not sortable." << std::endl;
    }
}

int main() {
    std::cout << "=== Basic Concepts Demo ===" << std::endl;

    int result1 = add(5, 3);
    std::cout << "add(5, 3) = " << result1 << std::endl;

    double result2 = add(3.14, 2.71);
    std::cout << "add(3.14, 2.71) = " << result2 << std::endl;

    // The following would cause a compilation error:
    // add("hello", "world");  // Error: "hello" is not a numeric type

    std::cout << "\n=== Concepts with Requirements ===" << std::endl;

    process(42, "hello");    // int is Numeric, string is Equality_comparable
    process(3.14, 42);       // double is Numeric, int is Equality_comparable

    // This would cause a compilation error:
    // struct NoEq {};
    // process(10, NoEq{});  // Error: NoEq is not Equality_comparable

    std::cout << "\n=== Constrained Class Templates ===" << std::endl;

    NumericContainer<int> intContainer;
    intContainer.add(1);
    intContainer.add(2);
    intContainer.add(3);

    std::cout << "Int container: ";
    intContainer.display();
    std::cout << "Sum: " << intContainer.sum() << std::endl;

    NumericContainer<double> doubleContainer;
    doubleContainer.add(1.5);
    doubleContainer.add(2.5);
    doubleContainer.add(3.5);

    std::cout << "Double container: ";
    doubleContainer.display();
    std::cout << "Sum: " << doubleContainer.sum() << std::endl;
    std::cout << "Average: " << doubleContainer.average() << std::endl;

    // This would cause a compilation error:
    // NumericContainer<std::string> strContainer;  // Error: std::string is not Numeric

    std::cout << "\n=== Requires Expressions ===" << std::endl;

    Person person("Alice", 30);
    std::cout << "Person to_string: " << person.to_string() << std::endl;

    // Check if types satisfy concepts
    check_sortable(5);                // int is Sortable
    check_sortable(person);           // Person is not Sortable (no < operator)

    std::cout << "\n=== Abbreviated Function Templates ===" << std::endl;

    print_if_numeric(10);       // Works with int
    print_if_numeric(3.14159);  // Works with double

    // This would cause a compilation error:
    // print_if_numeric("Not a number");  // Error: string is not Numeric

    return 0;
}