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;
}