Advanced 12 min

std::function

Master std::function, the polymorphic wrapper that can store any callable object for flexible callback systems

Learn std::function - the polymorphic wrapper that unifies all callable types for building flexible callback systems.

A Simple Example

#include <iostream>
#include <functional>
#include <vector>

// Regular function
int add(int a, int b) {
    return a + b;
}

// Functor
struct Multiply {
    int operator()(int a, int b) const {
        return a * b;
    }
};

int main() {
    // std::function can hold any callable with signature int(int, int)
    std::function<int(int, int)> operation;

    // Store function pointer
    operation = add;
    std::cout << "5 + 3 = " << operation(5, 3) << "\n";

    // Store lambda
    operation = [](int a, int b) { return a - b; };
    std::cout << "5 - 3 = " << operation(5, 3) << "\n";

    // Store functor
    operation = Multiply{};
    std::cout << "5 * 3 = " << operation(5, 3) << "\n";

    // Vector of functions
    std::vector<std::function<void()>> callbacks;

    callbacks.push_back([]() { std::cout << "Callback 1" << "\n"; });
    callbacks.push_back([]() { std::cout << "Callback 2" << "\n"; });

    std::cout << "\nExecuting callbacks:" << "\n";
    for (const auto& callback : callbacks) {
        callback();
    }

    return 0;
}

Breaking It Down

What is std::function?

  • What it is: A polymorphic wrapper that can store any callable with a matching signature
  • Syntax: std::function<ReturnType(Params)> - e.g., std::function<int(double, char)>
  • Stores: Function pointers, lambdas, functors, member functions (with std::bind)
  • Remember: It erases the specific type but preserves the signature

Type Erasure

  • What it does: Hides the actual type of the callable behind a common interface
  • How: Uses internal polymorphism (similar to virtual functions)
  • Trade-off: Runtime overhead (heap allocation, virtual dispatch) for flexibility
  • Remember: Similar to how void* erases pointer types, but type-safe

When to Use std::function

  • Runtime selection: When you need to choose which function to call at runtime
  • Containers: When storing multiple callables in vectors, maps, etc.
  • Callbacks: For event systems, GUI frameworks, async operations
  • Remember: Use templates when types are known at compile time for zero overhead

Performance Considerations

  • Overhead: Heap allocation for large captures, virtual dispatch for calls
  • Copying: std::function copies the wrapped callable
  • Alternative: For performance-critical code with known types, use templates
  • Remember: Measure if performance matters - often the flexibility is worth it

Why This Matters

  • std::function solves a fundamental problem: storing different types of callables (function pointers, lambdas, functors) in a uniform way.
  • It's essential for callbacks, event systems, strategy patterns, and any time you need to store or pass around functions as first-class objects.
  • Understanding std::function completes your knowledge of modern C++ callable objects.

Critical Insight

std::function uses type erasure - it hides the actual type of the callable behind a common interface, similar to how virtual functions work. This means there's runtime overhead (heap allocation for large captures, virtual dispatch) compared to templates or direct function calls.

But this overhead buys you incredible flexibility - you can store different callable types in containers, change them at runtime, and build sophisticated callback systems. Think of it like this: templates are compile-time polymorphism (fast, but types must be known), std::function is runtime polymorphism (flexible, but has cost).

Use templates when you know types at compile time, std::function when you need runtime flexibility. They solve different problems!

Best Practices

Match signatures exactly: Ensure the std::function signature matches the callable's return type and parameters.

Use for runtime polymorphism: Choose std::function when you need to store different callable types or select at runtime.

Prefer templates for performance: When types are known at compile time, templates avoid std::function's overhead.

Check for empty: Use if (func) to check if std::function is empty before calling to avoid std::bad_function_call.

Common Mistakes

Performance overhead: std::function has runtime cost (heap allocation, virtual dispatch). Use templates for hot paths.

Copying large captures: std::function copies the callable, which can be expensive for lambdas with large captures.

Calling empty std::function: Calling an empty std::function throws std::bad_function_call. Always check with if (func).

Signature mismatches: The std::function template signature must exactly match the callable. Even const differences matter.

Debug Challenge

This std::function has the wrong signature. Click the highlighted line to fix it:

1 #include <functional>
2
3 int main() {
4 std::function<void(int, int)> calc;
5 calc = [](int a, int b) { return a + b; };
6 int result = calc(5, 3);
7 }

Quick Quiz

  1. What's the syntax for std::function holding a function that takes int, returns double?
std::function<double(int)>
std::function<int(double)>
std::function<(int) -> double>
  1. When should you use std::function instead of templates?
Always - it's more flexible
When you need to store different callable types in containers or change them at runtime
Never - templates are always better
  1. What is the main cost of using std::function?
It's slower to compile
It uses more memory at compile time
Runtime overhead from heap allocation and virtual dispatch

Practice Playground

Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once