Standard Template Library (STL)
Master the powerful STL containers, algorithms, and utilities
Introduction to STL
The Standard Template Library (STL) is a collection of template classes and functions that provide common data structures and algorithms.
STL Components:
Containers
Data structures like vector, list, map, set, etc.
Iterators
Objects that point to elements in containers
Algorithms
Functions for searching, sorting, and manipulating data
Function Objects
Objects that can be called like functions
Benefits of STL: Reusable code, high performance, type safety, and extensive functionality tested by millions of developers.
Containers
STL containers are template classes that store collections of objects. They provide a consistent interface for managing data and are designed to work seamlessly with STL algorithms.
Container Categories:
Sequence Containers
Store elements in a linear sequence: vector, deque, list, array, forward_list
Associative Containers
Store elements in sorted order: set, multiset, map, multimap
Unordered Associative
Hash-based containers: unordered_set, unordered_multiset, unordered_map, unordered_multimap
Container Adaptors
Provide specialized interfaces: stack, queue, priority_queue
Performance Comparison
Container | Access | Insert/Delete Front | Insert/Delete Back | Insert/Delete Middle |
---|---|---|---|---|
vector | O(1) | O(n) | O(1) | O(n) |
deque | O(1) | O(1) | O(1) | O(n) |
list | O(n) | O(1) | O(1) | O(1) |
set/map | O(log n) | O(log n) | O(log n) | O(log n) |
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <set>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <stack>
#include <queue>
#include <array>
#include <forward_list>
#include <string>
#include <algorithm>
using namespace std;
int main() {
// Vector - Dynamic array (most commonly used)
cout << "=== Vector - Dynamic Array ===" << endl;
vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);
vec.insert(vec.begin() + 2, 10); // Insert 10 at index 2
vec.emplace_back(7); // Construct element in place
cout << "Vector elements: ";
for (const auto& elem : vec) {
cout << elem << " ";
}
cout << endl;
cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << endl;
cout << "Front: " << vec.front() << ", Back: " << vec.back() << endl;
// Vector operations
vec.reserve(20); // Reserve capacity
cout << "After reserve(20), capacity: " << vec.capacity() << endl;
vec.shrink_to_fit(); // Reduce capacity to size
cout << "After shrink_to_fit, capacity: " << vec.capacity() << endl;
// Array - Fixed-size container
cout << "\n=== Array - Fixed Size ===" << endl;
array<int, 5> arr = {10, 20, 30, 40, 50};
cout << "Array size: " << arr.size() << endl;
cout << "Array elements: ";
for (const auto& elem : arr) {
cout << elem << " ";
}
cout << endl;
// List - Doubly linked list
cout << "\n=== List - Doubly Linked List ===" << endl;
list<string> lst = {"apple", "banana", "cherry"};
lst.push_front("orange");
lst.push_back("grape");
lst.emplace_front("mango");
lst.sort();
cout << "List elements: ";
for (const auto& elem : lst) {
cout << elem << " ";
}
cout << endl;
// List unique operations
list<int> numbers = {1, 2, 2, 3, 3, 3, 4, 5, 5};
cout << "Before unique: ";
for (const auto& n : numbers) cout << n << " ";
cout << endl;
numbers.unique(); // Remove consecutive duplicates
cout << "After unique: ";
for (const auto& n : numbers) cout << n << " ";
cout << endl;
// Forward List - Singly linked list
cout << "\n=== Forward List - Singly Linked ===" << endl;
forward_list<int> flist = {1, 2, 3, 4, 5};
flist.push_front(0);
flist.emplace_front(-1);
cout << "Forward list: ";
for (const auto& elem : flist) {
cout << elem << " ";
}
cout << endl;
// Deque - Double-ended queue
cout << "\n=== Deque - Double-ended Queue ===" << endl;
deque<int> dq = {3, 4, 5};
dq.push_front(2);
dq.push_back(6);
dq.push_front(1);
dq.emplace_front(0);
dq.emplace_back(7);
cout << "Deque elements: ";
for (const auto& elem : dq) {
cout << elem << " ";
}
cout << endl;
cout << "Front: " << dq.front() << ", Back: " << dq.back() << endl;
// Set - Ordered unique elements
cout << "\n=== Set - Ordered Unique Elements ===" << endl;
set<int> s = {5, 2, 8, 2, 1, 9, 1}; // Duplicates automatically removed
s.insert(3);
s.insert(7);
s.emplace(4);
cout << "Set elements: ";
for (const auto& elem : s) {
cout << elem << " ";
}
cout << endl;
// Set operations
auto it = s.find(5);
if (it != s.end()) {
cout << "Found 5 in set" << endl;
}
cout << "Count of 5: " << s.count(5) << endl;
cout << "Lower bound of 5: " << *s.lower_bound(5) << endl;
cout << "Upper bound of 5: " << *s.upper_bound(5) << endl;
// Multiset - Ordered elements with duplicates
cout << "\n=== Multiset - Allows Duplicates ===" << endl;
multiset<int> ms = {5, 2, 8, 2, 1, 9, 1, 5, 5};
cout << "Multiset elements: ";
for (const auto& elem : ms) {
cout << elem << " ";
}
cout << endl;
cout << "Count of 5: " << ms.count(5) << endl;
// Map - Key-value pairs
cout << "\n=== Map - Key-Value Pairs ===" << endl;
map<string, int> ages;
ages["Alice"] = 25;
ages["Bob"] = 30;
ages["Charlie"] = 35;
ages.insert({"David", 28});
ages.emplace("Eve", 32);
cout << "Map elements:" << endl;
for (const auto& [name, age] : ages) {
cout << name << ": " << age << " years old" << endl;
}
// Map operations
if (ages.find("Alice") != ages.end()) {
cout << "Alice found with age: " << ages["Alice"] << endl;
}
// Unordered Set - Hash-based unique elements
cout << "\n=== Unordered Set - Hash-based Unique ===" << endl;
unordered_set<string> words = {"hello", "world", "cpp", "stl"};
words.insert("programming");
words.emplace("modern");
cout << "Unordered set elements: ";
for (const auto& word : words) {
cout << word << " ";
}
cout << endl;
cout << "Bucket count: " << words.bucket_count() << endl;
cout << "Load factor: " << words.load_factor() << endl;
// Unordered Map - Hash table
cout << "\n=== Unordered Map - Hash Table ===" << endl;
unordered_map<string, double> prices;
prices["apple"] = 1.50;
prices["banana"] = 0.75;
prices["orange"] = 2.00;
prices.emplace("grape", 3.25);
cout << "Prices:" << endl;
for (const auto& [item, price] : prices) {
cout << item << ": $" << price << endl;
}
cout << "Average access time: O(1)" << endl;
cout << "Bucket count: " << prices.bucket_count() << endl;
// Stack - LIFO container adaptor
cout << "\n=== Stack - LIFO Container ===" << endl;
stack<int> stk;
stk.push(10);
stk.push(20);
stk.push(30);
stk.emplace(40);
cout << "Stack size: " << stk.size() << endl;
cout << "Stack elements (popping): ";
while (!stk.empty()) {
cout << stk.top() << " ";
stk.pop();
}
cout << endl;
// Queue - FIFO container adaptor
cout << "\n=== Queue - FIFO Container ===" << endl;
queue<string> q;
q.push("first");
q.push("second");
q.push("third");
q.emplace("fourth");
cout << "Queue size: " << q.size() << endl;
cout << "Front: " << q.front() << ", Back: " << q.back() << endl;
cout << "Queue elements (dequeuing): ";
while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
cout << endl;
// Priority Queue - Heap-based container
cout << "\n=== Priority Queue - Max Heap ===" << endl;
priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);
pq.emplace(45);
cout << "Priority queue size: " << pq.size() << endl;
cout << "Priority queue elements (max heap): ";
while (!pq.empty()) {
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
// Min heap priority queue
priority_queue<int, vector<int>, greater<int>> min_pq;
min_pq.push(30);
min_pq.push(10);
min_pq.push(50);
min_pq.push(20);
cout << "Min heap priority queue: ";
while (!min_pq.empty()) {
cout << min_pq.top() << " ";
min_pq.pop();
}
cout << endl;
return 0;
}
Container Selection Tips:
- Use
vector
as default choice for sequential data - Use
deque
when you need efficient front/back operations - Use
list
for frequent insertions/deletions in the middle - Use
set/map
for sorted, unique data - Use
unordered_set/unordered_map
for fast lookups
Iterators
Iterators are objects that point to elements in containers and provide a way to traverse through them. They act as a bridge between containers and algorithms, enabling generic programming.
Iterator Categories:
Input Iterator
Read-only, forward movement only. Used for reading from input streams.
Operations: ++, *, ==, !=Output Iterator
Write-only, forward movement only. Used for writing to output streams.
Operations: ++, *Forward Iterator
Read/write, forward movement only. Used by forward_list, unordered containers.
Operations: ++, *, ==, !=, ->Bidirectional Iterator
Read/write, forward and backward movement. Used by list, set, map.
Operations: ++, --, *, ==, !=, ->Random Access Iterator
Read/write, jump to any position. Used by vector, deque, array.
Operations: ++, --, +, -, [], *, ==, !=, <, >, <=, >=#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
#include <iterator>
#include <algorithm>
#include <numeric>
#include <sstream>
using namespace std;
int main() {
// Basic iterator usage with vector (Random Access Iterator)
cout << "=== Random Access Iterator (vector) ===" << endl;
vector<int> vec = {10, 20, 30, 40, 50};
// Forward iteration
cout << "Forward iteration: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// Reverse iteration
cout << "Reverse iteration: ";
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
cout << *it << " ";
}
cout << endl;
// Constant iterators
cout << "Using const_iterator: ";
for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
cout << *it << " ";
// *it = 100; // Error! Cannot modify through const_iterator
}
cout << endl;
// Iterator arithmetic (random access iterators)
cout << "\n=== Iterator Arithmetic ===" << endl;
auto it = vec.begin();
cout << "First element: " << *it << endl;
cout << "Third element: " << *(it + 2) << endl;
cout << "Last element: " << *(vec.end() - 1) << endl;
// Distance between iterators
cout << "Distance from begin to end: " << distance(vec.begin(), vec.end()) << endl;
// Advance iterator
auto mid_it = vec.begin();
advance(mid_it, 2);
cout << "Element at position 2: " << *mid_it << endl;
// Next and prev iterators
auto next_it = next(vec.begin(), 3);
auto prev_it = prev(vec.end(), 2);
cout << "Next(begin, 3): " << *next_it << endl;
cout << "Prev(end, 2): " << *prev_it << endl;
// Modifying elements through iterators
cout << "\n=== Modifying Elements ===" << endl;
for (auto it = vec.begin(); it != vec.end(); ++it) {
*it *= 2; // Double each element
}
cout << "After doubling: ";
for (const auto& elem : vec) {
cout << elem << " ";
}
cout << endl;
// Bidirectional iterator (list)
cout << "\n=== Bidirectional Iterator (list) ===" << endl;
list<string> lst = {"apple", "banana", "cherry", "date"};
cout << "List forward: ";
for (auto it = lst.begin(); it != lst.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "List backward: ";
for (auto it = lst.rbegin(); it != lst.rend(); ++it) {
cout << *it << " ";
}
cout << endl;
// Finding in list
auto found = find(lst.begin(), lst.end(), "cherry");
if (found != lst.end()) {
cout << "Found: " << *found << endl;
// Cannot use arithmetic with bidirectional iterators
// cout << *(found + 1); // Error!
auto next_elem = next(found);
if (next_elem != lst.end()) {
cout << "Next element: " << *next_elem << endl;
}
}
// Forward iterator (unordered_set)
cout << "\n=== Forward Iterator (unordered_set) ===" << endl;
unordered_set<int> uset = {5, 2, 8, 1, 9};
cout << "Unordered set elements: ";
for (auto it = uset.begin(); it != uset.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// Map iterators
cout << "\n=== Map Iterators ===" << endl;
map<string, int> grades = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
cout << "Map elements:" << endl;
for (auto it = grades.begin(); it != grades.end(); ++it) {
cout << it->first << ": " << it->second << endl;
// Alternatively: (*it).first, (*it).second
}
// Lower and upper bounds with iterators
auto lower = grades.lower_bound("Bob");
auto upper = grades.upper_bound("Bob");
cout << "Lower bound for Bob: " << lower->first << endl;
if (upper != grades.end()) {
cout << "Upper bound for Bob: " << upper->first << endl;
}
// Insert iterators
cout << "\n=== Insert Iterators ===" << endl;
vector<int> source = {1, 2, 3, 4, 5};
vector<int> dest;
// Back insert iterator
copy(source.begin(), source.end(), back_inserter(dest));
cout << "After back_inserter: ";
for (const auto& elem : dest) {
cout << elem << " ";
}
cout << endl;
// Front insert iterator (with list)
list<int> destList;
copy(source.begin(), source.end(), front_inserter(destList));
cout << "After front_inserter: ";
for (const auto& elem : destList) {
cout << elem << " ";
}
cout << endl;
// General insert iterator
vector<int> destInsert = {100, 200};
auto insert_pos = destInsert.begin() + 1;
copy(source.begin(), source.begin() + 3, inserter(destInsert, insert_pos));
cout << "After inserter: ";
for (const auto& elem : destInsert) {
cout << elem << " ";
}
cout << endl;
// Stream iterators
cout << "\n=== Stream Iterators ===" << endl;
vector<int> numbers = {1, 2, 3, 4, 5};
cout << "Using ostream_iterator: ";
copy(numbers.begin(), numbers.end(), ostream_iterator<int>(cout, " "));
cout << endl;
// Input stream iterator
cout << "Enter some integers (end with non-integer): ";
vector<int> input_numbers;
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(input_numbers));
cout << "You entered: ";
copy(input_numbers.begin(), input_numbers.end(), ostream_iterator<int>(cout, " "));
cout << endl;
// String stream with iterators
stringstream ss("10 20 30 40 50");
vector<int> stream_numbers;
copy(istream_iterator<int>(ss), istream_iterator<int>(), back_inserter(stream_numbers));
cout << "From stringstream: ";
for (const auto& num : stream_numbers) {
cout << num << " ";
}
cout << endl;
// Reverse iterators
cout << "\n=== Reverse Iterators ===" << endl;
vector<int> vals = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Find last even number
auto rev_it = find_if(vals.rbegin(), vals.rend(), [](int n) { return n % 2 == 0; });
if (rev_it != vals.rend()) {
cout << "Last even number: " << *rev_it << endl;
// Convert reverse iterator to normal iterator
auto normal_it = rev_it.base();
cout << "Position from begin: " << distance(vals.begin(), normal_it) - 1 << endl;
}
// Iterator invalidation example
cout << "\n=== Iterator Safety ===" << endl;
vector<int> safety_vec = {1, 2, 3, 4, 5};
auto safe_it = safety_vec.begin() + 2;
cout << "Before push_back: " << *safe_it << endl;
// This might invalidate iterators if reallocation occurs
safety_vec.reserve(20); // Ensure no reallocation
safety_vec.push_back(6);
cout << "After push_back (with reserve): " << *safe_it << endl;
return 0;
}
Iterator Safety Tips:
- Iterators can be invalidated by container modifications
- Use const_iterator when you don't need to modify elements
- Prefer range-based for loops when iterator arithmetic isn't needed
- Be careful with iterator arithmetic - only works with random access iterators
Algorithms
STL algorithms are template functions that perform operations on ranges of elements. They work with any container that provides the appropriate iterators, making them highly reusable and efficient.
Algorithm Categories:
Non-modifying
Read elements without changing them
find, count, equal, search, for_eachModifying
Change or rearrange elements
copy, move, transform, replace, removeSorting
Arrange elements in order
sort, stable_sort, partial_sort, nth_elementNumeric
Mathematical operations
accumulate, adjacent_difference, inner_productSet Operations
Work with sorted ranges
merge, set_union, set_intersectionTime Complexity Guide:
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
#include <functional>
#include <iterator>
#include <list>
#include <deque>
#include <set>
#include <random>
#include <execution>
using namespace std;
// Custom predicate functions
bool isEven(int n) { return n % 2 == 0; }
bool isOdd(int n) { return n % 2 == 1; }
struct Person {
string name;
int age;
double salary;
Person(string n, int a, double s) : name(n), age(a), salary(s) {}
};
int main() {
// Sorting algorithms - The foundation of many operations
cout << "=== Sorting Algorithms ===" << endl;
vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};
cout << "Original: ";
for (const auto& n : numbers) cout << n << " ";
cout << endl;
// Sort ascending
sort(numbers.begin(), numbers.end());
cout << "Sort ascending: ";
for (const auto& n : numbers) cout << n << " ";
cout << endl;
// Sort descending
sort(numbers.begin(), numbers.end(), greater<int>());
cout << "Sort descending: ";
for (const auto& n : numbers) cout << n << " ";
cout << endl;
// Stable sort (preserves relative order of equal elements)
vector<Person> people = {
{"Alice", 25, 50000}, {"Bob", 30, 60000}, {"Charlie", 25, 55000}
};
stable_sort(people.begin(), people.end(),
[](const Person& a, const Person& b) { return a.age < b.age; });
cout << "Stable sort by age:" << endl;
for (const auto& p : people) {
cout << p.name << " (age " << p.age << ")" << endl;
}
// Partial sort - only sort first N elements
vector<int> partial_nums = {9, 5, 2, 8, 1, 7, 3, 6, 4};
partial_sort(partial_nums.begin(), partial_nums.begin() + 3, partial_nums.end());
cout << "Partial sort (first 3): ";
for (const auto& n : partial_nums) cout << n << " ";
cout << endl;
// nth_element - partition around nth element
vector<int> nth_nums = {9, 5, 2, 8, 1, 7, 3, 6, 4};
nth_element(nth_nums.begin(), nth_nums.begin() + 4, nth_nums.end());
cout << "After nth_element (4th position): ";
for (const auto& n : nth_nums) cout << n << " ";
cout << " (5th element: " << nth_nums[4] << ")" << endl;
// Searching algorithms
cout << "\n=== Searching Algorithms ===" << endl;
vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Linear search
auto it = find(data.begin(), data.end(), 5);
if (it != data.end()) {
cout << "Linear search - Found 5 at position: " << distance(data.begin(), it) << endl;
}
// Find with predicate
auto even_it = find_if(data.begin(), data.end(), isEven);
if (even_it != data.end()) {
cout << "First even number: " << *even_it << endl;
}
// Find if not
auto odd_it = find_if_not(data.begin(), data.end(), isEven);
if (odd_it != data.end()) {
cout << "First odd number: " << *odd_it << endl;
}
// Binary search (requires sorted data)
bool found = binary_search(data.begin(), data.end(), 7);
cout << "Binary search for 7: " << (found ? "Found" : "Not found") << endl;
// Lower and upper bound
auto lower = lower_bound(data.begin(), data.end(), 5);
auto upper = upper_bound(data.begin(), data.end(), 5);
cout << "Lower bound of 5: position " << distance(data.begin(), lower) << endl;
cout << "Upper bound of 5: position " << distance(data.begin(), upper) << endl;
// Equal range
auto range = equal_range(data.begin(), data.end(), 5);
cout << "Equal range for 5: [" << distance(data.begin(), range.first)
<< ", " << distance(data.begin(), range.second) << ")" << endl;
// Search for subsequence
vector<int> pattern = {3, 4, 5};
auto sub_it = search(data.begin(), data.end(), pattern.begin(), pattern.end());
if (sub_it != data.end()) {
cout << "Found pattern {3,4,5} at position: " << distance(data.begin(), sub_it) << endl;
}
// Counting algorithms
cout << "\n=== Counting Algorithms ===" << endl;
vector<int> values = {1, 2, 3, 2, 4, 2, 5, 2, 6};
int count2 = count(values.begin(), values.end(), 2);
cout << "Count of 2: " << count2 << endl;
int countEven = count_if(values.begin(), values.end(), isEven);
cout << "Count of even numbers: " << countEven << endl;
// All, any, none algorithms
bool allPositive = all_of(values.begin(), values.end(), [](int n) { return n > 0; });
bool anyGreaterThan5 = any_of(values.begin(), values.end(), [](int n) { return n > 5; });
bool noneNegative = none_of(values.begin(), values.end(), [](int n) { return n < 0; });
cout << "All positive: " << allPositive << endl;
cout << "Any greater than 5: " << anyGreaterThan5 << endl;
cout << "None negative: " << noneNegative << endl;
// Transformation algorithms
cout << "\n=== Transformation Algorithms ===" << endl;
vector<int> source = {1, 2, 3, 4, 5};
vector<int> squares;
// Transform to squares
transform(source.begin(), source.end(), back_inserter(squares),
[](int n) { return n * n; });
cout << "Original: ";
for (const auto& n : source) cout << n << " ";
cout << endl;
cout << "Squares: ";
for (const auto& n : squares) cout << n << " ";
cout << endl;
// Binary transform (two input ranges)
vector<int> source2 = {10, 20, 30, 40, 50};
vector<int> sums;
transform(source.begin(), source.end(), source2.begin(), back_inserter(sums),
[](int a, int b) { return a + b; });
cout << "Sums: ";
for (const auto& n : sums) cout << n << " ";
cout << endl;
// Replace algorithms
vector<int> replace_vec = {1, 2, 3, 2, 4, 2, 5};
replace(replace_vec.begin(), replace_vec.end(), 2, 99);
cout << "After replace(2, 99): ";
for (const auto& n : replace_vec) cout << n << " ";
cout << endl;
// Replace if
replace_if(replace_vec.begin(), replace_vec.end(),
[](int n) { return n > 50; }, 0);
cout << "After replace_if(>50, 0): ";
for (const auto& n : replace_vec) cout << n << " ";
cout << endl;
// Copy algorithms
cout << "\n=== Copy Algorithms ===" << endl;
vector<int> copy_source = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> copy_dest;
// Copy all
copy(copy_source.begin(), copy_source.end(), back_inserter(copy_dest));
cout << "Copy all: ";
for (const auto& n : copy_dest) cout << n << " ";
cout << endl;
// Copy if
vector<int> even_dest;
copy_if(copy_source.begin(), copy_source.end(), back_inserter(even_dest), isEven);
cout << "Copy even: ";
for (const auto& n : even_dest) cout << n << " ";
cout << endl;
// Copy n elements
vector<int> copy_n_dest;
copy_n(copy_source.begin(), 5, back_inserter(copy_n_dest));
cout << "Copy first 5: ";
for (const auto& n : copy_n_dest) cout << n << " ";
cout << endl;
// Remove algorithms
cout << "\n=== Remove Algorithms ===" << endl;
vector<int> remove_vec = {1, 2, 3, 2, 4, 2, 5, 2, 6};
cout << "Before remove: ";
for (const auto& n : remove_vec) cout << n << " ";
cout << endl;
// Remove doesn't actually erase, it moves elements
auto new_end = remove(remove_vec.begin(), remove_vec.end(), 2);
cout << "After remove(2): ";
for (auto it = remove_vec.begin(); it != new_end; ++it) cout << *it << " ";
cout << endl;
// Erase-remove idiom for actual removal
remove_vec.erase(new_end, remove_vec.end());
cout << "After erase: ";
for (const auto& n : remove_vec) cout << n << " ";
cout << endl;
// Numeric algorithms
cout << "\n=== Numeric Algorithms ===" << endl;
vector<int> nums = {1, 2, 3, 4, 5};
int sum = accumulate(nums.begin(), nums.end(), 0);
cout << "Sum: " << sum << endl;
int product = accumulate(nums.begin(), nums.end(), 1, multiplies<int>());
cout << "Product: " << product << endl;
// Reduce (C++17) - parallel version of accumulate
int sum2 = reduce(nums.begin(), nums.end(), 0);
cout << "Reduce sum: " << sum2 << endl;
// Partial sum
vector<int> partialSums;
partial_sum(nums.begin(), nums.end(), back_inserter(partialSums));
cout << "Partial sums: ";
for (const auto& n : partialSums) cout << n << " ";
cout << endl;
// Adjacent difference
vector<int> differences;
adjacent_difference(nums.begin(), nums.end(), back_inserter(differences));
cout << "Adjacent differences: ";
for (const auto& n : differences) cout << n << " ";
cout << endl;
// Iota - fill with consecutive values
vector<int> iota_vec(10);
iota(iota_vec.begin(), iota_vec.end(), 1);
cout << "Iota (1-10): ";
for (const auto& n : iota_vec) cout << n << " ";
cout << endl;
// Inner product
vector<int> vec1 = {1, 2, 3, 4};
vector<int> vec2 = {5, 6, 7, 8};
int dot_product = inner_product(vec1.begin(), vec1.end(), vec2.begin(), 0);
cout << "Dot product: " << dot_product << endl;
// Min/Max algorithms
cout << "\n=== Min/Max Algorithms ===" << endl;
vector<int> elements = {3, 1, 4, 1, 5, 9, 2, 6};
auto minIt = min_element(elements.begin(), elements.end());
auto maxIt = max_element(elements.begin(), elements.end());
auto minMaxPair = minmax_element(elements.begin(), elements.end());
cout << "Min element: " << *minIt << " at position " << distance(elements.begin(), minIt) << endl;
cout << "Max element: " << *maxIt << " at position " << distance(elements.begin(), maxIt) << endl;
cout << "MinMax: min=" << *minMaxPair.first << ", max=" << *minMaxPair.second << endl;
// Clamp (C++17)
cout << "Clamp 15 to [1,10]: " << clamp(15, 1, 10) << endl;
cout << "Clamp -5 to [1,10]: " << clamp(-5, 1, 10) << endl;
// Permutation algorithms
cout << "\n=== Permutation Algorithms ===" << endl;
vector<int> perm = {1, 2, 3};
cout << "All permutations of {1, 2, 3}:" << endl;
do {
for (const auto& n : perm) cout << n << " ";
cout << endl;
} while (next_permutation(perm.begin(), perm.end()));
// Check if one sequence is a permutation of another
vector<int> seq1 = {1, 2, 3, 4};
vector<int> seq2 = {4, 3, 2, 1};
bool is_perm = is_permutation(seq1.begin(), seq1.end(), seq2.begin());
cout << "Is {4,3,2,1} a permutation of {1,2,3,4}? " << is_perm << endl;
// Set operations (require sorted input)
cout << "\n=== Set Operations ===" << endl;
vector<int> set1 = {1, 2, 3, 4, 5};
vector<int> set2 = {3, 4, 5, 6, 7};
vector<int> result;
// Union
set_union(set1.begin(), set1.end(), set2.begin(), set2.end(), back_inserter(result));
cout << "Union: ";
for (const auto& n : result) cout << n << " ";
cout << endl;
// Intersection
result.clear();
set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), back_inserter(result));
cout << "Intersection: ";
for (const auto& n : result) cout << n << " ";
cout << endl;
// Difference
result.clear();
set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(), back_inserter(result));
cout << "Difference (set1 - set2): ";
for (const auto& n : result) cout << n << " ";
cout << endl;
// Symmetric difference
result.clear();
set_symmetric_difference(set1.begin(), set1.end(), set2.begin(), set2.end(), back_inserter(result));
cout << "Symmetric difference: ";
for (const auto& n : result) cout << n << " ";
cout << endl;
// Heap operations
cout << "\n=== Heap Operations ===" << endl;
vector<int> heap_vec = {3, 1, 4, 1, 5, 9, 2, 6};
// Make heap
make_heap(heap_vec.begin(), heap_vec.end());
cout << "After make_heap: ";
for (const auto& n : heap_vec) cout << n << " ";
cout << " (max: " << heap_vec.front() << ")" << endl;
// Push to heap
heap_vec.push_back(10);
push_heap(heap_vec.begin(), heap_vec.end());
cout << "After push 10: max = " << heap_vec.front() << endl;
// Pop from heap
pop_heap(heap_vec.begin(), heap_vec.end());
int max_val = heap_vec.back();
heap_vec.pop_back();
cout << "Popped max: " << max_val << ", new max: " << heap_vec.front() << endl;
// Sort heap
sort_heap(heap_vec.begin(), heap_vec.end());
cout << "After sort_heap: ";
for (const auto& n : heap_vec) cout << n << " ";
cout << endl;
// Shuffle algorithm
cout << "\n=== Shuffle Algorithm ===" << endl;
vector<int> shuffle_vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
random_device rd;
mt19937 g(rd());
cout << "Original: ";
for (const auto& n : shuffle_vec) cout << n << " ";
cout << endl;
shuffle(shuffle_vec.begin(), shuffle_vec.end(), g);
cout << "Shuffled: ";
for (const auto& n : shuffle_vec) cout << n << " ";
cout << endl;
return 0;
}
Algorithm Best Practices:
- Always use the most specific algorithm for your task (e.g., binary_search vs find)
- Prefer algorithms over hand-written loops - they're optimized and less error-prone
- Use lambda expressions for simple predicates and transformations
- Remember the erase-remove idiom for actually removing elements
- Consider parallel algorithms (C++17) for large datasets
Function Objects
Function objects (functors) are objects that can be called like functions. They provide a way to encapsulate function-like behavior in objects and are fundamental to STL's flexibility.
Types of Function Objects
Type | Syntax | Performance | Use Cases |
---|---|---|---|
Lambda Expressions | [capture](params) { body } |
Excellent (inlined) | Modern C++, local use |
Built-in Functors | std::plus<T>() |
Excellent (optimized) | Standard operations |
Custom Functors | operator() |
Excellent (inlined) | Complex state, reusability |
Function Pointers | RetType (*func)(Args) |
Good (call overhead) | C compatibility |
std::function | std::function<Sig> |
Fair (type erasure) | Type erasure, storage |
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>
#include <memory>
using namespace std;
// Custom functor classes
class Multiplier {
int factor;
public:
Multiplier(int f) : factor(f) {}
int operator()(int x) const { return x * factor; }
// For demonstration - functors can have state
void setFactor(int f) { factor = f; }
int getFactor() const { return factor; }
};
class Accumulator {
mutable int sum = 0; // mutable allows modification in const operator()
public:
int operator()(int value) const {
sum += value;
return sum;
}
int getSum() const { return sum; }
};
// Predicate functors
struct IsEven {
bool operator()(int n) const { return n % 2 == 0; }
};
struct InRange {
int min, max;
InRange(int mn, int mx) : min(mn), max(mx) {}
bool operator()(int n) const { return n >= min && n <= max; }
};
int main() {
cout << "=== Lambda Expressions ===" << endl;
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Basic lambda
auto square = [](int x) { return x * x; };
cout << "Square of 5: " << square(5) << endl;
// Lambda with capture by value
int multiplier = 3;
auto multiplyBy = [multiplier](int x) { return x * multiplier; };
cout << "7 * 3 = " << multiplyBy(7) << endl;
// Lambda with capture by reference
int sum = 0;
auto addToSum = [&sum](int x) { sum += x; return sum; };
cout << "Adding 5 to sum: " << addToSum(5) << endl;
cout << "Adding 10 to sum: " << addToSum(10) << endl;
// Lambda with mutable (allows modifying captured-by-value variables)
int counter = 0;
auto increment = [counter]() mutable { return ++counter; };
cout << "Counter: " << increment() << ", " << increment() << ", " << increment() << endl;
cout << "Original counter still: " << counter << endl;
// Generic lambda (C++14)
auto genericAdd = [](auto a, auto b) { return a + b; };
cout << "Generic add (int): " << genericAdd(5, 3) << endl;
cout << "Generic add (double): " << genericAdd(2.5, 1.5) << endl;
cout << "Generic add (string): " << genericAdd(string("Hello "), string("World")) << endl;
// Lambda with explicit return type
auto divide = [](double a, double b) -> double {
if (b == 0) throw invalid_argument("Division by zero");
return a / b;
};
cout << "10.0 / 3.0 = " << divide(10.0, 3.0) << endl;
// Capture all by value and reference
int a = 10, b = 20;
auto captureAll = [=, &b](int x) { b += x; return a + b + x; }; // a by value, b by reference
cout << "Capture example: " << captureAll(5) << " (b is now " << b << ")" << endl;
// Immediately invoked lambda expression (IILE)
int result = [](int x, int y) { return x * y + 10; }(5, 3);
cout << "IILE result: " << result << endl;
cout << "\n=== Built-in Functors ===" << endl;
// Arithmetic functors
plus<int> add;
minus<int> subtract;
multiplies<int> multiply;
divides<int> divide_op;
modulus<int> mod;
negate<int> neg;
cout << "5 + 3 = " << add(5, 3) << endl;
cout << "5 - 3 = " << subtract(5, 3) << endl;
cout << "5 * 3 = " << multiply(5, 3) << endl;
cout << "15 / 3 = " << divide_op(15, 3) << endl;
cout << "15 % 4 = " << mod(15, 4) << endl;
cout << "-5 = " << neg(5) << endl;
// Comparison functors
equal_to<int> eq;
not_equal_to<int> neq;
greater<int> gt;
less<int> lt;
greater_equal<int> gte;
less_equal<int> lte;
cout << "5 == 5: " << eq(5, 5) << endl;
cout << "5 != 3: " << neq(5, 3) << endl;
cout << "5 > 3: " << gt(5, 3) << endl;
// Logical functors
logical_and<bool> and_op;
logical_or<bool> or_op;
logical_not<bool> not_op;
cout << "true && false: " << and_op(true, false) << endl;
cout << "true || false: " << or_op(true, false) << endl;
cout << "!true: " << not_op(true) << endl;
// Using functors with algorithms
vector<int> nums = {5, 2, 8, 1, 9};
sort(nums.begin(), nums.end(), greater<int>());
cout << "Sorted descending: ";
for (int n : nums) cout << n << " ";
cout << endl;
cout << "\n=== Custom Functors ===" << endl;
// Using custom functors
Multiplier times3(3);
vector<int> source = {1, 2, 3, 4, 5};
vector<int> multiplied;
transform(source.begin(), source.end(), back_inserter(multiplied), times3);
cout << "Multiplied by 3: ";
for (int n : multiplied) cout << n << " ";
cout << endl;
// Functor with state
Accumulator acc;
cout << "Accumulating: ";
for (int i = 1; i <= 5; ++i) {
cout << acc(i) << " ";
}
cout << "(final sum: " << acc.getSum() << ")" << endl;
// Predicate functors
IsEven evenChecker;
InRange rangeChecker(3, 7);
auto evenCount = count_if(source.begin(), source.end(), evenChecker);
auto inRangeCount = count_if(source.begin(), source.end(), rangeChecker);
cout << "Even numbers in source: " << evenCount << endl;
cout << "Numbers in range [3,7]: " << inRangeCount << endl;
cout << "\n=== std::function and Type Erasure ===" << endl;
// std::function can hold any callable
function<int(int)> func;
// Assign lambda
func = [](int x) { return x * 2; };
cout << "Lambda: " << func(5) << endl;
// Assign functor
func = times3;
cout << "Functor: " << func(5) << endl;
// Assign function pointer
func = [](int x) { return x + 10; };
cout << "Another lambda: " << func(5) << endl;
// Vector of functions
vector<function<int(int)>> operations = {
[](int x) { return x * x; }, // square
[](int x) { return x * 2; }, // double
[](int x) { return x + 1; }, // increment
Multiplier(5) // multiply by 5
};
cout << "Applying operations to 3: ";
for (const auto& op : operations) {
cout << op(3) << " ";
}
cout << endl;
cout << "\n=== Function Binding ===" << endl;
// Function to bind
auto addThree = [](int a, int b, int c) { return a + b + c; };
// Bind some arguments
auto addTo10And20 = bind(addThree, placeholders::_1, 10, 20);
cout << "Adding 5 to (10 + 20): " << addTo10And20(5) << endl;
// Bind with reordered arguments
auto reorderedAdd = bind(addThree, placeholders::_3, placeholders::_1, placeholders::_2);
cout << "Reordered add(1,2,3) as (3,1,2): " << reorderedAdd(1, 2, 3) << endl;
// Bind member function
struct Calculator {
int multiply(int a, int b) const { return a * b; }
};
Calculator calc;
auto boundMultiply = bind(&Calculator::multiply, &calc, placeholders::_1, placeholders::_2);
cout << "Bound member function: " << boundMultiply(6, 7) << endl;
cout << "\n=== Advanced Function Object Techniques ===" << endl;
// Function composition
auto compose = [](auto f, auto g) {
return [f, g](auto x) { return f(g(x)); };
};
auto increment = [](int x) { return x + 1; };
auto square_func = [](int x) { return x * x; };
auto incrementThenSquare = compose(square_func, increment);
cout << "Compose square(increment(5)): " << incrementThenSquare(5) << endl;
// Currying
auto curry = [](auto f) {
return [f](auto a) {
return [f, a](auto b) {
return f(a, b);
};
};
};
auto curriedAdd = curry([](int a, int b) { return a + b; });
auto add5 = curriedAdd(5);
cout << "Curried add(5)(3): " << add5(3) << endl;
// Higher-order function
auto applyTwice = [](auto f) {
return [f](auto x) { return f(f(x)); };
};
auto doubleFunc = [](int x) { return x * 2; };
auto doubleTwice = applyTwice(doubleFunc);
cout << "Apply double twice to 3: " << doubleTwice(3) << endl;
return 0;
}
Function Object Best Practices:
- Prefer lambdas for simple, local operations
- Use custom functors when you need state or complex logic
- std::function is great for type erasure but has performance overhead
- Capture by reference [&] for large objects, by value [=] for primitives
- Use mutable lambdas sparingly - prefer external state management
- Consider generic lambdas for template-like behavior
Utilities
STL provides various utility classes and functions for common programming tasks, including smart pointers, type traits, time utilities, and modern C++ features.
Utility Categories
Category | Key Components | C++ Version | Primary Use Cases |
---|---|---|---|
Pairs & Tuples | std::pair , std::tuple |
C++98/C++11 | Multiple return values, data grouping |
Smart Pointers | unique_ptr , shared_ptr , weak_ptr |
C++11 | Automatic memory management |
Type Traits | is_same , enable_if , conditional |
C++11/C++14 | Template metaprogramming |
Time & Chrono | chrono::duration , time_point |
C++11 | Time measurement, timing |
Random Numbers | random_device , mt19937 |
C++11 | High-quality random generation |
Optional & Variant | std::optional , std::variant |
C++17 | Safe optionals, type-safe unions |
#include <iostream>
#include <utility>
#include <tuple>
#include <string>
#include <vector>
#include <algorithm>
#include <chrono>
#include <random>
#include <memory>
#include <optional>
#include <variant>
#include <type_traits>
#include <functional>
#include <any>
#include <string_view>
#include <span>
using namespace std;
// Example classes for demonstrations
class Resource {
public:
Resource(const string& name) : name_(name) {
cout << "Resource '" << name_ << "' created\n";
}
~Resource() {
cout << "Resource '" << name_ << "' destroyed\n";
}
void use() const {
cout << "Using resource: " << name_ << "\n";
}
string getName() const { return name_; }
private:
string name_;
};
// Function returning multiple values using pair
pair<bool, int> divideWithRemainder(int a, int b) {
if (b == 0) return {false, 0};
return {true, a % b};
}
// Function returning multiple values using tuple
tuple<double, double, double> calculateStats(const vector<int>& data) {
if (data.empty()) return {0.0, 0.0, 0.0};
double sum = accumulate(data.begin(), data.end(), 0.0);
double mean = sum / data.size();
double variance = 0.0;
for (const auto& val : data) {
variance += (val - mean) * (val - mean);
}
variance /= data.size();
double stddev = sqrt(variance);
return {mean, variance, stddev};
}
// Template function demonstrating type traits
template<typename T>
void processNumber(T value) {
if constexpr (is_integral_v<T>) {
cout << "Processing integer: " << value << " (doubled: " << value * 2 << ")\n";
} else if constexpr (is_floating_point_v<T>) {
cout << "Processing float: " << value << " (squared: " << value * value << ")\n";
} else {
cout << "Processing other type\n";
}
}
int main() {
cout << "=== Pairs and Tuples ===" << endl;
// Pair examples
pair<string, int> person("Alice", 25);
cout << "Person: " << person.first << ", Age: " << person.second << endl;
auto coordinates = make_pair(10.5, 20.3);
cout << "Coordinates: (" << coordinates.first << ", " << coordinates.second << ")" << endl;
// Using pair for multiple return values
auto [success, remainder] = divideWithRemainder(17, 5);
if (success) {
cout << "17 % 5 = " << remainder << endl;
}
// Tuple examples
tuple<string, int, double, bool> student("Bob", 22, 3.8, true);
cout << "Student: " << get<0>(student) << ", Age: " << get<1>(student)
<< ", GPA: " << get<2>(student) << ", Enrolled: " << get<3>(student) << endl;
// Structured binding (C++17)
auto [name, age, gpa, enrolled] = student;
cout << "Using structured binding - Name: " << name << ", Age: " << age << endl;
// Tuple from function
vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto [mean, variance, stddev] = calculateStats(data);
cout << "Stats - Mean: " << mean << ", Variance: " << variance << ", StdDev: " << stddev << endl;
// Tuple operations
cout << "Tuple size: " << tuple_size_v<decltype(student)> << endl;
cout << "\n=== Smart Pointers ===" << endl;
// unique_ptr - exclusive ownership
cout << "-- unique_ptr --" << endl;
{
auto resource1 = make_unique<Resource>("UniqueResource");
resource1->use();
// Transfer ownership
auto resource2 = move(resource1);
if (!resource1) {
cout << "resource1 is now null after move" << endl;
}
resource2->use();
// Custom deleter
auto customDeleter = [](Resource* r) {
cout << "Custom deleter called for: " << r->getName() << endl;
delete r;
};
unique_ptr<Resource, decltype(customDeleter)> customResource(
new Resource("CustomDeleted"), customDeleter
);
} // Resources automatically destroyed here
cout << "-- shared_ptr --" << endl;
{
auto shared1 = make_shared<Resource>("SharedResource");
cout << "Reference count: " << shared1.use_count() << endl;
{
auto shared2 = shared1; // Copy increases reference count
cout << "Reference count after copy: " << shared1.use_count() << endl;
shared2->use();
} // shared2 goes out of scope
cout << "Reference count after shared2 destroyed: " << shared1.use_count() << endl;
// weak_ptr to break cycles
weak_ptr<Resource> weak = shared1;
cout << "weak_ptr expired: " << weak.expired() << endl;
if (auto locked = weak.lock()) {
locked->use();
cout << "Successfully locked weak_ptr" << endl;
}
}
cout << "\n=== Optional and Variant ===" << endl;
// Optional (C++17)
auto safeDivide = [](double a, double b) -> optional<double> {
if (b == 0.0) return nullopt;
return a / b;
};
auto result1 = safeDivide(10.0, 2.0);
auto result2 = safeDivide(10.0, 0.0);
if (result1) {
cout << "Division result: " << *result1 << endl;
}
cout << "Safe division by zero: " << result2.value_or(-1.0) << endl;
// Variant (C++17) - type-safe union
variant<int, double, string> value;
value = 42;
cout << "Variant holds int: " << get<int>(value) << endl;
value = 3.14;
cout << "Variant holds double: " << get<double>(value) << endl;
value = string("Hello");
cout << "Variant holds string: " << get<string>(value) << endl;
// Visiting variant
auto visitor = [](const auto& v) {
cout << "Visiting value: " << v << " (type: " << typeid(v).name() << ")" << endl;
};
visit(visitor, value);
cout << "\n=== Type Traits and SFINAE ===" << endl;
// Basic type checking
cout << "is_integral<int>: " << is_integral_v<int> << endl;
cout << "is_floating_point<double>: " << is_floating_point_v<double> << endl;
cout << "is_same<int, int32_t>: " << is_same_v<int, int32_t> << endl;
cout << "is_pointer<int*>: " << is_pointer_v<int*> << endl;
// Template function with type traits
processNumber(42); // int
processNumber(3.14); // double
processNumber("hello"); // const char*
// enable_if example
auto processContainer = []<typename T>(const T& container) {
if constexpr (is_same_v<T, vector<typename T::value_type>>) {
cout << "Processing vector with " << container.size() << " elements" << endl;
} else {
cout << "Processing other container type" << endl;
}
};
vector<int> vec = {1, 2, 3, 4, 5};
processContainer(vec);
cout << "\n=== Chrono Utilities ===" << endl;
// Time measurement
auto start = chrono::high_resolution_clock::now();
// Simulate work
vector<int> largeVec(100000);
iota(largeVec.begin(), largeVec.end(), 1);
sort(largeVec.begin(), largeVec.end(), greater<int>());
auto end = chrono::high_resolution_clock::now();
// Different time units
auto microsecs = chrono::duration_cast<chrono::microseconds>(end - start);
auto millisecs = chrono::duration_cast<chrono::milliseconds>(end - start);
auto nanosecs = chrono::duration_cast<chrono::nanoseconds>(end - start);
cout << "Sorting took:" << endl;
cout << " " << microsecs.count() << " microseconds" << endl;
cout << " " << millisecs.count() << " milliseconds" << endl;
cout << " " << nanosecs.count() << " nanoseconds" << endl;
// Time points and durations
using namespace chrono_literals;
auto now = chrono::system_clock::now();
auto future = now + 1h + 30min + 45s;
cout << "Time calculations work with literals!" << endl;
cout << "\n=== Random Number Generation ===" << endl;
random_device rd; // Hardware random number generator
mt19937 gen(rd()); // Mersenne Twister generator
// Different distributions
uniform_int_distribution<int> intDist(1, 100);
uniform_real_distribution<double> realDist(0.0, 1.0);
normal_distribution<double> normalDist(50.0, 15.0); // mean=50, stddev=15
bernoulli_distribution coinFlip(0.5);
cout << "Uniform integers (1-100): ";
for (int i = 0; i < 5; ++i) cout << intDist(gen) << " ";
cout << endl;
cout << "Uniform reals (0.0-1.0): ";
for (int i = 0; i < 5; ++i) cout << realDist(gen) << " ";
cout << endl;
cout << "Normal distribution (μ=50, σ=15): ";
for (int i = 0; i < 5; ++i) cout << static_cast<int>(normalDist(gen)) << " ";
cout << endl;
cout << "Coin flips: ";
for (int i = 0; i < 10; ++i) cout << (coinFlip(gen) ? "H" : "T");
cout << endl;
// Shuffle with random generator
vector<string> cards = {"A", "2", "3", "4", "5", "J", "Q", "K"};
shuffle(cards.begin(), cards.end(), gen);
cout << "Shuffled cards: ";
for (const auto& card : cards) cout << card << " ";
cout << endl;
cout << "\n=== Modern C++ Utilities ===" << endl;
// std::any (C++17) - type-safe container for any type
any anyValue;
anyValue = 42;
cout << "any contains int: " << any_cast<int>(anyValue) << endl;
anyValue = string("Hello World");
cout << "any contains string: " << any_cast<string>(anyValue) << endl;
// string_view (C++17) - non-owning string reference
string_view processString(string_view sv) {
cout << "Processing string_view: " << sv << " (length: " << sv.length() << ")" << endl;
return sv.substr(0, 5); // No copying!
}
string longString = "This is a very long string that we don't want to copy";
auto firstPart = processString(longString);
cout << "First part: " << firstPart << endl;
// span (C++20) - view over contiguous sequence
auto processArray = [](span<const int> arr) {
cout << "Processing array of size " << arr.size() << ": ";
for (const auto& elem : arr) cout << elem << " ";
cout << endl;
};
int cArray[] = {1, 2, 3, 4, 5};
vector<int> cppArray = {6, 7, 8, 9, 10};
processArray(span<const int>(cArray, 5));
processArray(span<const int>(cppArray));
// Move semantics utilities
cout << "\n=== Move and Perfect Forwarding ===" << endl;
vector<string> source = {"apple", "banana", "cherry"};
cout << "Source size before move: " << source.size() << endl;
vector<string> destination = move(source);
cout << "Source size after move: " << source.size() << endl;
cout << "Destination size: " << destination.size() << endl;
// Perfect forwarding example
auto perfectForwarder = []<typename T>(T&& value) {
return forward<T>(value);
};
string testStr = "Hello";
auto forwarded1 = perfectForwarder(testStr); // lvalue reference
auto forwarded2 = perfectForwarder(move(testStr)); // rvalue reference
cout << "Perfect forwarding preserves value categories" << endl;
// std::exchange (C++14) - sets new value and returns old
int oldValue = 10;
int newValue = exchange(oldValue, 20);
cout << "exchange: old=" << newValue << ", new=" << oldValue << endl;
return 0;
}
Utility Best Practices:
- Use smart pointers for automatic memory management - prefer make_unique/make_shared
- std::optional is safer than nullable pointers for optional values
- std::variant provides type-safe unions - better than C-style unions
- Use chrono for all time-related operations - avoid platform-specific timing
- Prefer structured bindings for accessing tuple/pair elements (C++17+)
- string_view avoids unnecessary string copies - great for function parameters
- Use type traits for compile-time type checking and SFINAE techniques