C++: STL Adapters
In lesson 38 we learned about function objects.
Now, we'll learn about function adapters — transforming existing functions into the form you need.
Like LEGO bricks, using small pieces to build something bigger.
1. Adapter Overview
(1) 1.1 What Are Adapters?
Adapters are templates that modify the behavior of function objects.
Common adapters:
std::bind(bind arguments)std::ref(pass by reference)std::negate(negation)std::mem_fn(member function pointer)
2. std::bind — Argument Binding
(1) 2.1 Basic Usage
std::bind is used to bind function arguments, creating new function objects.
Example: Binding arguments (Difficulty ⭐⭐)
▶ Example 2: Code Example (Difficulty ⭐)
#include <iostream>
#include <functional>
int add(int a, int b) {
return a + b;
}
int main() {
// Bind the first argument of add to 10
auto add10 = std::bind(add, 10, std::placeholders::_1);
std::cout << "add10(5) = " << add10(5) << std::endl; // Output: 15
std::cout << "add10(20) = " << add10(20) << std::endl; // Output: 30
return 0;
}
Output:
add10(5) = 15
add10(20) = 30
💡 Tip:
std::placeholders::_1means "leave the first argument to be filled in later"
(2) 2.2 Reordering Arguments
Example: Swapping argument order (Difficulty ⭐⭐)
#include <iostream>
#include <functional>
int subtract(int a, int b) {
return a - b;
}
int main() {
// Swap argument order
auto reverse_subtract = std::bind(subtract,
std::placeholders::_2,
std::placeholders::_1);
std::cout << "subtract(10, 3) = " << subtract(10, 3) << std::endl; // 7
std::cout << "reverse(10, 3) = " << reverse_subtract(10, 3) << std::endl; // -7
return 0;
}
3. std::ref — Reference Wrapping
(1) 3.1 The Problem: Pass by Value
By default, STL algorithms pass function objects by value, which means state cannot be shared.
Example: Solving with std::ref (Difficulty ⭐⭐)
#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>
struct Counter {
int count = 0;
void operator()(int) { count++; }
};
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
Counter counter;
// ❌ Wrong! Pass by value, a copy of counter is called
std::for_each(v.begin(), v.end(), counter);
std::cout << "Count: " << counter.count << std::endl; // Output: 0
// ✅ Correct! Use std::ref to pass by reference
std::for_each(v.begin(), v.end(), std::ref(counter));
std::cout << "Count: " << counter.count << std::endl; // Output: 5
return 0;
}
4. std::not_fn — Negation
(1) 4.1 Basic Usage
std::not_fn is used to negate the return value of a function object.
Example: Negating a predicate (Difficulty ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Find the first even number
auto it1 = std::find_if(v.begin(), v.end(),
(int x) { return x % 2 == 0; });
std::cout << "First even: " << *it1 << std::endl; // 2
// Find the first odd number (negation)
auto it2 = std::find_if(v.begin(), v.end(),
std::not_fn((int x) { return x % 2 == 0; }));
std::cout << "First odd: " << *it2 << std::endl; // 1
return 0;
}
5. std::mem_fn — Member Function Pointer
(1) 5.1 The Problem: Member Function Pointers Are Hard to Use
Member function pointer syntax is complex; std::mem_fn simplifies it.
Example: Calling member functions (Difficulty ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
struct Student {
std::string name;
void display() const {
std::cout << "Student: " << name << std::endl;
}
};
int main() {
std::vectorStudent students = {{"Zhang San"}, {"Li Si"}};
// Use std::mem_fn to call member function
std::for_each(students.begin(), students.end(),
std::mem_fn(&Student::display));
return 0;
}
6. Comprehensive Example
▶ Example 1: Flexible Score Processor (Difficulty ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
int threshold = 90;
// Count how many scores are not lower than threshold
int count = std::count_if(scores.begin(), scores.end(),
std::bind(std::greater_equalint(),
std::placeholders::_1,
threshold));
std::cout << "Number of scores >= " << threshold << ": " << count << std::endl;
return 0;
}
Output:
Number of scores >= :
❓ FAQ
Q: Is std::bind still relevant in C++11? A: Yes, but Lambda is more recommended. Lambda is more concise and performs better.
// Using std::bind
auto f1 = std::bind(add, 10, std::placeholders::_1);
// Using Lambda (recommended)
auto f2 = (int x) { return add(10, x); };
stack<int, vector<int>> uses vector; queue<int, list<int>> uses list. By default, stack uses deque, and queue also uses deque.▶ Example 3: Queue (Difficulty ⭐)
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
q.push(10);
q.push(20);
q.push(30);
while (!q.empty()) {
std::cout << "Front: " << q.front() << std::endl;
q.pop();
}
return 0;
}
Output:
Front:
push() to enqueue, front() to access the front element, and pop() to dequeue.
📖 Summary
std::stack: stack adapter, Last-In-First-Out (LIFO)std::queue: queue adapter, First-In-First-Out (FIFO)std::priority_queue: priority queue, dequeues by prioritystd::bind: function argument binding
📝 Exercises
-
Basic (Difficulty ⭐): Create a
stackint, push 1, 2, 3 in order, then loop and pop all elements. Observe the output order. -
Intermediate (Difficulty ⭐⭐): Use
queueto implement a "print job queue" — simulate multiple print jobs being processed in order, outputting the remaining queue length after each job is processed. -
Challenge (Difficulty ⭐⭐⭐): Use
priority_queueto implement a "task scheduler" — each task has a priority (1-10), the queue processes tasks from highest to lowest priority, with ties broken by insertion order.
- Adapters: stack/queue/priority_queue wrap underlying containers
- stack LIFO: push/pop/top
- queue FIFO: push/pop/front/back
- priority_queue priority queue: max-heap
- Adapters specify underlying container through template parameters
Next lesson: Exception Handling (#40)