std::function is a general-purpose C++ callable wrapper that lets one variable hold and invoke many different kinds of callable targets through one fixed function signature. A free function, lambda, functor, member-function pointer, or other compatible callable can be hidden behind the same interface when the program needs runtime flexibility instead of a compile-time template parameter.
This lesson follows OSC++.031: std::any, OSC++.030: std::variant, and OSC++.029: std::optional. Those lessons introduced vocabulary types and type erasure; std::function applies type erasure specifically to behavior.
Lesson Navigation
- Mental model
- The function signature
- What can be stored
- Callbacks and higher-order design
- Empty state and exceptions
- Type erasure and cost
- Modern alternatives
- Hands-on lab
- Knowledge check
Mental Model: One Socket, Many Callables
Think of std::function<R(Args...)> as a socket with a fixed calling shape. The return type and parameter list are fixed, but the concrete object plugged into that socket can vary at runtime. The wrapper owns its target and exposes one common operation: call it.
std::function.The Signature Is the Contract
#include <functional>
std::function<int(int, int)> operation;
intbefore the parentheses is the return type.int, intinside the parentheses are the parameter types.- Any compatible callable can be assigned if it can be invoked with two
intarguments and produce a result compatible withint. - The wrapper type does not encode the concrete callable type.
Quick check: what does std::function<void(double)> mean?
It is a callable wrapper whose interface accepts one double argument and returns void.
What Can std::function Store?
| Callable form | Example | State? |
|---|---|---|
| Free function | int add(int, int) | No object state |
| Function pointer | &add | No object state |
| Lambda | [scale](int x){ return x * scale; } | Can capture state |
| Functor | Object with operator() | Yes |
| Member-function pointer | &Widget::run | Requires an object argument in the wrapper signature |
#include <functional>
#include <iostream>
int add(int a, int b) {
return a + b;
}
struct Multiply {
int factor{};
int operator()(int value) const {
return value * factor;
}
};
int main() {
std::function<int(int, int)> binary = add;
std::cout << binary(2, 3) << '\n';
int offset = 10;
std::function<int(int)> lambda = [offset](int x) {
return x + offset;
};
std::cout << lambda(5) << '\n';
std::function<int(int)> functor = Multiply{4};
std::cout << functor(6) << '\n';
}
Predict the output before opening
5, then 15, then 24.
std::function.Lambdas Are Function Objects
- A lambda expression creates a closure object with a compiler-generated type.
- A capturing lambda can carry state with it.
std::functionlets code erase that unique closure type and store the lambda behind a named function signature.- That flexibility is especially useful at API boundaries where the concrete lambda type cannot conveniently appear in a non-template interface.
std::function.Callbacks and Higher-Order Design
#include <functional>
#include <iostream>
#include <string>
void run_job(const std::string& name,
const std::function<void(const std::string&)>& on_complete) {
std::cout << "Running " << name << '\n';
on_complete(name);
}
int main() {
run_job("diagnostic", [](const std::string& job) {
std::cout << job << " complete\n";
});
}
- The function receiving the callback does not need to know whether the caller passed a lambda, free function, or functor.
- The callback signature documents exactly what the receiving function is allowed to call.
- This pattern appears in event systems, asynchronous completion handlers, GUI actions, test hooks, command dispatch, networking, and hardware-control software.
Empty std::function Objects
std::function<void()> task;
if (task) {
task();
}
- A default-constructed
std::functionhas no target. operator bool()reports whether a target is present.- Calling an empty wrapper throws
std::bad_function_call. - Checking the wrapper before invocation is the normal defensive pattern when emptiness is possible.
Why not just call it and catch the exception?
Because an empty callback can often be an expected state. Testing if (task) communicates that expectation directly and avoids using exceptions for routine branching.
How Type Erasure Makes This Work
The concrete callable type is hidden behind a uniform runtime interface. Internally, implementations typically keep enough machinery to invoke, copy, move, and destroy the stored callable without exposing its concrete type through the public wrapper. That is why several unrelated callable types can occupy the same std::function<R(Args...)> variable.
std::function and examines the runtime tradeoffs.Copyability Matters
std::functionitself is copyable.- The target stored inside it must satisfy the required copyability constraints.
- A lambda that owns a move-only object by value may therefore fail to fit
std::function. - Modern C++ offers
std::move_only_functionin C++23 when a move-only callable wrapper is the correct abstraction.
#include <functional>
#include <memory>
int main() {
auto ptr = std::make_unique<int>(42);
auto move_only_lambda = [value = std::move(ptr)]() {
return *value;
};
// std::function<int()> f = move_only_lambda;
// This is not the right wrapper for a move-only target.
}
Performance: Flexibility Is Not Free
- Invocation usually requires indirect dispatch through erased runtime machinery.
- Some implementations use small-buffer optimization so small callables can live inside the wrapper itself.
- Larger callables may require dynamic allocation.
- The exact small-buffer size is an implementation detail.
- Templates can often inline concrete callables more aggressively because the callable type stays visible to the compiler.
- Performance-sensitive code should benchmark the actual workload instead of assuming the wrapper cost is either negligible or unacceptable.
std::function, including its tradeoffs and internal design.Choose the Wrapper That Matches Ownership
| Tool | Core idea | Best fit |
|---|---|---|
std::function | Owning, copyable callable wrapper | General callbacks and runtime-polymorphic behavior |
std::move_only_function C++23 | Owning, move-only wrapper | Callbacks carrying move-only state |
std::function_ref C++26 | Non-owning callable reference | Short-lived borrowed callables when lifetime is externally guaranteed |
| Template parameter | Callable type stays concrete | Compile-time generic code and maximum optimization opportunity |
| Function pointer | Pointer to a function | Simple stateless C-style callback interfaces |
Decision helper
If the callback must own arbitrary copyable state and cross a non-template interface, start by considering std::function. If the callable is move-only, consider std::move_only_function. If ownership is not needed and C++26 is available, std::function_ref may be a better fit. If the callable type can stay known at compile time, a template is often simpler and faster.
Common Mistakes
- Forgetting that an empty wrapper throws when called.
- Using
std::functionin a hot inner loop without measuring the cost. - Capturing large objects by value and assuming the wrapper will never allocate.
- Trying to store a move-only callable in
std::function. - Choosing runtime type erasure when a simple template parameter would keep the design clearer.
- Capturing references in a lambda and then allowing the referenced objects to die before the callback runs.
Hands-On Lab: Build a Command Dispatcher
#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
int main() {
std::unordered_map<std::string, std::function<void()>> commands;
int counter = 0;
commands["status"] = [] {
std::cout << "system ready\n";
};
commands["increment"] = [&counter] {
++counter;
std::cout << "counter = " << counter << '\n';
};
commands["status"]();
commands["increment"]();
commands["increment"]();
}
- Compile with
g++ -std=c++20 -Wall -Wextra -pedantic main.cpp -o app. - Run the program and confirm the dispatcher invokes two different lambdas through one wrapper type.
- Add a free function called
reset_system()and store it under the keyreset. - Add a functor that prints a diagnostic message and store it under
diagnose. - Before invoking a command, use
find()so an unknown key does not accidentally create an empty wrapper. - Add one callback that captures a string by value and another that captures an integer by reference. Explain the lifetime difference.
Challenge: Predict the Failure
std::unordered_map<std::string, std::function<void()>> commands;
commands["missing"]();
Reveal the failure
operator[] inserts a default-constructed empty std::function for the missing key. Invoking that empty wrapper throws std::bad_function_call. A lookup with find() or contains() avoids the accidental insertion and lets the program handle the missing command explicitly.
Knowledge Check
- What does the template argument in
std::function<int(double)>describe? - Can a capturing lambda be stored in
std::function? - What happens if an empty
std::functionis invoked? - What operation checks whether a target is present?
- Why can
std::functionhide many different callable types behind one wrapper type? - Why might a template parameter be faster?
- What C++23 wrapper is designed for move-only callable targets?
- What lifetime risk appears when a stored lambda captures local variables by reference?
1. Reveal answer
It describes the callable signature: return int and accept one double.
2. Reveal answer
Yes, if the closure object satisfies the wrapper’s requirements and its call operator matches the requested signature.
3. Reveal answer
It throws std::bad_function_call.
4. Reveal answer
Use the wrapper’s boolean state, commonly if (fn).
5. Reveal answer
Type erasure hides the concrete callable type behind one runtime-polymorphic interface.
6. Reveal answer
A template keeps the concrete callable type visible to the compiler, often allowing direct calls and more aggressive inlining.
7. Reveal answer
std::move_only_function.
8. Reveal answer
The callback may outlive the referenced object, creating a dangling reference. The captured object must remain alive for every invocation.
References
Primary technical references: cppreference — std::function and cppreference — function objects and wrappers. The standard library defines std::function in <functional> and has provided it since C++11.
Key Takeaway
std::function turns many concrete callable types into one stable runtime interface. Use it when the program genuinely benefits from owning and replacing arbitrary copyable callbacks behind one signature. Prefer simpler or more specialized alternatives when compile-time dispatch, move-only ownership, borrowed lifetime, or maximum performance better matches the problem.
Rotation next: Semiconductor Technician.
BitcoinVersus.Tech Editor’s Note:
We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support to help further secure the integrity of our research initiatives, please donate using the QR code provided

For best QR-code scanning results, please scan this donation code from a desktop or larger screen. Mobile screens may prevent camera recognition.

Leave a Reply