std::function gives C++ one stable, copyable wrapper for many different callable types. A free function, function pointer, lambda expression, functor, bind expression, or compatible member-function target can all be presented through the same signature such as std::function<int(int, int)>. This lesson follows OSC++.031: std::any, OSC++.030: std::variant, and OSC++.029: std::optional. The common theme is interface design: when should type information stay visible at compile time, and when is runtime type erasure the better engineering trade?
std::function implementation and exposes the mechanics behind the abstraction.Lesson Navigation
- Learning objectives
- The C++ callable model
- Your first std::function
- Storing lambdas and functors
- How type erasure works
- Empty wrappers and bad_function_call
- Inspecting the stored target
- Performance and allocation
- Modern alternatives
- Decision table
- Hands-on exercises
- Knowledge check
Learning Objectives
- Define what a callable object is in modern C++.
- Declare
std::function<R(Args...)>with the correct call signature. - Store free functions, function pointers, lambdas, and function objects.
- Use
operator boolto test whether a wrapper has a target. - Explain why invoking an empty wrapper throws
std::bad_function_call. - Inspect stored targets with
target_type()andtarget<T>(). - Explain type erasure without confusing it with type conversion.
- Recognize the copyability requirement imposed on stored targets.
- Understand why type erasure can add indirection and sometimes allocation.
- Choose among templates, raw function pointers,
std::function,std::move_only_function, andstd::function_ref.
The C++ Callable Model
| Callable form | Example | Own state? | Can std::function wrap it? |
|---|---|---|---|
| Free function | int add(int, int) | No | Yes |
| Function pointer | int (*p)(int, int) | No | Yes |
| Captureless lambda | [](int x){ return x * 2; } | No captured state | Yes |
| Capturing lambda | [factor](int x){ return x * factor; } | Yes | Yes, if copy-constructible |
| Functor | Multiplier{3} | Usually | Yes, if copy-constructible |
| Member-function target | &Widget::run | Object supplied at invocation | Yes |
Your First std::function
#include <functional>
#include <iostream>
int add(int a, int b)
{
return a + b;
}
int main()
{
std::function<int(int, int)> operation = add;
std::cout << operation(7, 5) << '\n';
}
std::functionis declared in<functional>.- The template argument describes the call signature, not the concrete stored type.
int(int, int)means “callable with twointarguments and producing anintresult.”- The wrapper is invoked with normal function-call syntax:
operation(7, 5).
g++ -std=c++20 -Wall -Wextra -pedantic main.cpp -o callable_demo
./callable_demo
Storing Lambdas and Functors
#include <functional>
#include <iostream>
struct Multiplier
{
int factor;
int operator()(int value) const
{
return value * factor;
}
};
int main()
{
int offset = 10;
std::function<int(int)> transform =
[offset](int value)
{
return value + offset;
};
std::cout << transform(5) << '\n';
transform = Multiplier{4};
std::cout << transform(5) << '\n';
}
- The first target is a capturing lambda containing its own copy of
offset. - The second target is a function object whose
operator()implements multiplication. - The concrete target type changes while the visible wrapper type remains
std::function<int(int)>. - This ability is the practical value of type erasure: callers depend on the signature rather than the concrete callable class.
How Type Erasure Works
std::function performs type erasure: it preserves the behavior needed to copy, destroy, and invoke a target while hiding that target’s concrete C++ type behind one runtime wrapper. The wrapper does not convert every callable into a literal function pointer. Instead, it stores enough implementation machinery to dispatch a call through the declared signature. This lets unrelated callable types share one container or API boundary while retaining type-safe argument and return-value checking.
std::function can be built from simpler mechanisms and then optimized.Empty Wrappers and std::bad_function_call
#include <functional>
#include <iostream>
int main()
{
std::function<void()> task;
if (!task)
{
std::cout << "No target installed\n";
}
try
{
task();
}
catch (const std::bad_function_call& e)
{
std::cout << e.what() << '\n';
}
}
- A default-constructed
std::functionhas no target. if (task)checks whether a target is installed.- Invoking an empty
std::functionthrowsstd::bad_function_call. - For normal callback APIs, checking the wrapper before invocation is usually clearer than intentionally relying on the exception.
Inspecting the Stored Target
#include <functional>
#include <iostream>
#include <typeinfo>
int square(int x)
{
return x * x;
}
int main()
{
std::function<int(int)> f = square;
std::cout << f.target_type().name() << '\n';
if (auto p = f.target<int(*)(int)>())
{
std::cout << (*p)(9) << '\n';
}
}
target_type()reports the stored target’s runtime type.target<T>()returns a pointer when the requested type exactly matches the stored target type.- A mismatch returns
nullptr. - Target inspection is useful for diagnostics and specialized adapters, but ordinary code should usually invoke through the wrapper’s public signature.
Performance, Allocation, and Copyability
The flexibility of std::function is not free. Type erasure normally adds an indirect dispatch step, and a sufficiently large target may require dynamic storage; implementations commonly use a small-object optimization for some compact callables, but programs should not assume one universal inline-buffer size. The stored target must also satisfy the wrapper’s copyability requirements, which means a lambda that owns a move-only object cannot generally be placed into std::function. Performance-sensitive paths should benchmark the actual implementation rather than assigning one fixed cost to every call.
folly::Function explains why move-only callables expose an important limitation of classic std::function.Modern Alternatives
| Tool | Standard | Ownership | Copyable? | Best fit |
|---|---|---|---|---|
std::function | C++11 | Owning | Yes | General reusable callbacks with copyable targets |
std::move_only_function | C++23 | Owning | No | Callbacks that may capture move-only state |
std::copyable_function | C++26 | Owning | Yes | Newer copyable wrapper with richer qualifiers |
std::function_ref | C++26 | Non-owning | Lightweight reference semantics | Temporary callable views when lifetime is guaranteed externally |
| Template parameter | Language feature | Depends on caller | Depends on type | Compile-time generic code and maximum optimization opportunity |
| Raw function pointer | Language feature | Non-owning | Trivial | Simple stateless free-function callbacks |
Decision Table
- Need one runtime callback slot that can hold different copyable callables? Use
std::function. - Need to capture a
std::unique_ptror another move-only resource? Considerstd::move_only_functionin C++23. - Need no ownership and the caller guarantees lifetime? Consider
std::function_refwhen C++26 is available, or a carefully designed alternative. - Know the callable type at compile time and want maximum inlining opportunity? Prefer a template parameter.
- Only free functions with one exact signature are allowed? A raw function pointer may be sufficient.
Callback Registry Example
#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
using Handler = std::function<void(const std::string&)>;
int main()
{
std::unordered_map<std::string, Handler> handlers;
handlers["log"] = [](const std::string& msg)
{
std::cout << "LOG: " << msg << '\n';
};
int warnings = 0;
handlers["warn"] = [&warnings](const std::string& msg)
{
++warnings;
std::cout << "WARN: " << msg << '\n';
};
handlers.at("log")("System ready");
handlers.at("warn")("Temperature high");
std::cout << "Warnings: " << warnings << '\n';
}
Hands-On Exercises
- Create
std::function<double(double, double)>and assign a normal function that multiplies two values. - Replace the target with a lambda that divides the first argument by the second.
- Create a functor named
Powerthat stores an exponent and invokesstd::pow. - Store
Power{2}instd::function<double(double)>. - Default-construct an empty callback and verify it with
operator bool. - Intentionally invoke the empty callback inside
try/catchand identifystd::bad_function_call. - Create a capturing lambda that owns a
std::unique_ptr<int>. Attempt to place it instd::function, observe the compile-time failure, then repeat withstd::move_only_functionunder C++23. - Build a small event registry using
std::unordered_map<std::string, std::function<void()>>.
Checkpoint: Why does std::function need a signature?
The signature defines the stable public calling contract: return type, argument count, and argument types. Concrete callable types can differ internally as long as they are compatible with that contract.
Checkpoint: Why can a move-only lambda fail?
Classic std::function is copyable and requires the stored target to satisfy its copy-construction requirements. A lambda that directly owns a move-only resource may therefore be incompatible. C++23 std::move_only_function was designed for this class of use case.
Knowledge Check
- Which header defines
std::function? - Which C++ standard introduced it?
- What does the type
std::function<bool(int)>describe? - Name four categories of callable target that it can wrap.
- What happens when an empty
std::functionis invoked? - How can code test whether a wrapper currently has a target?
- What does
target<T>()return whenTdoes not match? - Why can a move-only capture be a problem?
- Which C++23 wrapper is designed for move-only callables?
- When can a template parameter be preferable to
std::function?
Show knowledge-check answers
<functional>.- C++11.
- A callable interface that returns
booland accepts oneint. - Examples: free functions, function pointers, lambdas, functors, bind expressions, and compatible member targets.
- It throws
std::bad_function_call. - Use its contextual boolean conversion, such as
if (callback). nullptr.- The stored target for classic
std::functionmust satisfy copyability requirements. std::move_only_function.- When the callable type is known at compile time and static dispatch, inlining opportunity, or zero-overhead genericity matters.
References
- cppreference: std::function
- cppreference: std::bad_function_call
- cppreference: std::move_only_function
- cppreference: std::copyable_function
- cppreference: std::function_ref
Key Takeaway
std::functionis an owning, copyable, type-erased callable wrapper.- Its template argument specifies the call signature rather than the concrete target type.
- It is excellent for callback registries, event systems, task interfaces, and APIs that need one stable runtime callable type.
- Templates remain stronger when the concrete callable can stay visible at compile time.
- C++23 and C++26 add more specialized wrappers for move-only, richer copyable, and non-owning callable use cases.
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