Templates[temp]

std::function in C++: what it costs, what to use instead

Section
Templates [temp]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
14 min read
In this answer10 clauses
  1. 1What std::function holds
  2. 2std::function vs a function pointer
  3. 3Calling an empty std::function
  4. 4Does std::function allocate? Size and allocations per library
  5. 5A std::function owns a copy of its callable
  6. 6Move-only callables and const: std::move_only_function
  7. 7Taking a callable as a parameter
  8. 8std::bind or a lambda
  9. 9std::invoke: one way to call any callable
  10. 10Which to use

Abstract

std::function<R(Args...)> stores a copy of any copyable callable with that signature (a function, a lambda with captures, a function object, a pointer to a member function) and usually calls it through a pointer. It may allocate, and on x86-64 it is 32 to 64 bytes, depending on the standard library. Take a template parameter when a function only calls its argument, a function pointer for a C interface, and std::move_only_function (C++23; in libstdc++ and Microsoft's STL, not libc++ 23) for a callable that cannot be copied.

Three identical rectangular frames in a row, holding a hatched circle, a hatched triangle and a hatched square; the middle frame’s outline is drawn in blue.
Figure 1 — Different callables, one type to hold them.

std::function<R(Args...)> is a copyable object holding a copy of any copy-constructible callable with that signature: a function, a lambda with or without captures, a function object, a member function pointer. Use it to store callables whose types are not known where they are stored: a callback member, a list of handlers. The price: it is several pointers big, it may allocate, and a call usually goes through a pointer. A function that only calls its argument should take a template parameter; a function pointer holds only functions and lambdas with an empty capture list, [].

This answer is written for C++17. std::function itself is C++11 (Since When’s C++11 table has its row); C++17 gave it a deduction guide, added std::invoke, and removed the allocator constructors and assign that older references still list. C++20 added std::bind_front, C++23 std::move_only_function and std::bind_back, and C++26 adds std::copyable_function and std::function_ref.

The samples were built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++): the first two on Windows x64, sharing Microsoft’s standard library; GCC with libstdc++ and Clang 23.1 with libc++ on Compiler Explorer, Linux x86-64. MSVC and Clang 19.1 printed the same everywhere, so the tables have one column per library. Nothing was timed: the samples count bytes and allocations, and the counts were the same with and without optimization.

What changed: arguments are bound to a function with std::bind and placeholders a lambda.

since C++14

What std::function holds

One std::function<double(double)> takes a function, then a lambda that captures an offset, then a function object; a member function goes in a std::function whose first parameter is the object:

[Example 1:
double to_fahrenheit(double celsius) { return celsius * 1.8 + 32.0; }

struct Scale {  // a function object: a factor and a call operator
    double factor;
    double operator()(double v) const { return v * factor; }
};
// ...
std::function<double(double)> convert = to_fahrenheit;
std::printf("%.1f\n", convert(20.0));  // 68.0

double offset = -0.5;
convert = [offset](double v) { return v + offset; };
std::printf("%.1f\n", convert(20.0));  // 19.5

convert = Scale{1000.0};
std::printf("%.1f\n", convert(20.0));  // 20000.0

std::function<double(const Sensor&)> read = &Sensor::value;
Sensor probe(21.5);
std::printf("%.1f\n", read(probe));  // 21.5

std::function fahrenheit = to_fahrenheit;  // C++17: deduced
std::function shifted = [offset](double v) { return v + offset; };

— end example]

All four printed what the comments say, and both deductions gave std::function<double(double)>. What goes in must be copy-constructible and callable with the signature’s arguments ([func.wrap.func]).

Storing is the job it exists for: callables of different types in one member or one container. A Pipeline of conversion steps:

[Example 2:
class Pipeline {
public:
    void add(std::function<double(double)> step) {
        steps_.push_back(std::move(step));
    }
    double run(double value) const {
        for (const auto& step : steps_) value = step(value);
        return value;
    }

private:
    std::vector<std::function<double(double)>> steps_;
};
// ...
double offset = -0.5;
Pipeline pipeline;
pipeline.add(Scale{0.1});  // raw tenths of a degree to degrees
pipeline.add([offset](double v) { return v + offset; });
pipeline.add(to_fahrenheit);
std::printf("%.2f\n", pipeline.run(215.0));  // 69.80

— end example]

It printed 69.80 on all four. Two std::function objects cannot be compared with each other, only with nullptr, so a list of handlers that must remove one keeps an id or a token beside each.

std::function vs a function pointer

A function pointer holds the address of a function and nothing else: 8 bytes on both platforms tested, no allocation, and the type a C API or a table of plain functions wants. A using alias makes it readable:

[Example 3:
using Conversion = double (*)(double);  // a pointer to such a function
// ...
double (*convert)(double) = to_fahrenheit;  // & is optional
std::printf("%.2f\n", convert(20.0));  // 68.00

Conversion table[] = {to_fahrenheit, to_kelvin};
for (Conversion c : table) std::printf("%.2f\n", c(20.0));

convert = [](double v) { return v * 1000.0; };  // no captures
std::printf("%.2f\n", convert(20.0));  // 20000.00

Conversion none = nullptr;
if (!none) std::puts("no function");  // test it; never call it

— end example]

The table printed 68.00 and 293.15 on all four. A lambda with an empty capture list converts to a function pointer; one that captures does not:

[Example 4:
double (*convert)(double) =
    [offset](double v) { return v + offset; };  // error

— end example]

All four rejected it, and none named the capture. GCC’s line is the shortest: cannot convert 'main()::<lambda(double)>' to 'double (*)(double)' in initialization. The reason: a captured value needs somewhere to live, and a function pointer points only at code. Only a lambda with an empty capture list has a conversion to a function pointer, which is what MSVC’s note means by No user-defined-conversion operator available; a [=] or [&] lambda that uses nothing has none either, on all four.

Calling an empty std::function

A default-constructed std::function is empty: it converts to false, and calling it throws std::bad_function_call ([func.wrap.func.inv]). One made from a null function pointer is empty too:

[Example 5:
std::function<double(double)> convert;  // holds nothing
if (!convert) std::puts("empty");

try {
    convert(20.0);
} catch (const std::bad_function_call& e) {
    std::printf("caught: %s\n", e.what());
}

double (*none)(double) = nullptr;
std::function<double(double)> wrapped = none;  // empty, too
std::printf("wrapped holds a target: %s\n", wrapped ? "yes" : "no");
try {
    wrapped(20.0);  // throws: it never calls the null pointer
} catch (const std::bad_function_call&) {
    std::puts("caught again");
}

— end example]

All four printed empty, a caught: line, wrapped holds a target: no and caught again. Each library words what() its own way, so catch the type, not the message. Calling a null function pointer directly is undefined behavior; the std::function built from one threw instead.

Does std::function allocate? Size and allocations per library

It may, and when depends on the library. The standard says only that implementations “should avoid the use of dynamically allocated memory for small callable objects”, and promises only that building one from a function pointer or a std::reference_wrapper, or copying one that holds either, throws nothing ([func.wrap.func.con]). Each library keeps a callable inside the std::function object only when it is small enough and passes a second test of its own:

On x86-64 Microsoft’s STL libstdc++ libc++
sizeof(std::function<void()>) 64 32 48
sizeof a function pointer 8 8 8
Callable kept inside, up to 48 bytes 16 bytes 24 bytes
And only if the callable is movable without throwing trivially copyable copyable without throwing

Any other callable lives on the heap, and the object points to it:

Two std::function boxes, each split into a small buffer and a how-to-call-it cell. One holds a small lambda in its buffer; the other’s buffer points to a larger lambda on the heap. In both, an indirect-call arrow reaches the lambda.
Figure 2 — A std::function keeps a small callable inside itself if its library allows, and a larger one on the heap; either way a call usually goes through a pointer.

A replaced global operator new counted the allocations, reset before constructing a std::function<void()> from each callable and again before copying it:

[Example 6:
int allocations = 0;  // every call to operator new

void* operator new(std::size_t size) {
    ++allocations;
    if (void* p = std::malloc(size ? size : 1)) return p;
    throw std::bad_alloc{};
}
// ...
template <class F>
void measure(const char* what, F callable) {
    allocations = 0;
    std::function<void()> stored = std::move(callable);
    int constructed = allocations;

    allocations = 0;
    std::function<void()> copy = stored;
    int copied = allocations;
    // ...

— end example]

Allocations made by the construction, in release builds; copying the std::function made the same count again, in every row, on every library:

Callable (bytes) Microsoft’s STL libstdc++ libc++
Function pointer (8) 0 0 0
Lambda, 2 pointers (16) 0 0 0
Lambda, 3 pointers (24) 0 1 0
Lambda, 4 pointers (32) 0 1 1
Lambda, short std::string (32; 24 on libc++) 0 1 1
Lambda, std::shared_ptr (16) 0 1 0
Lambda, 64-byte array (64) 1 1 1
std::ref(lambda) (8) 0 0 0

Size is not the whole rule. The std::shared_ptr lambda is 16 bytes and allocated on libstdc++ alone, because it is not trivially copyable. The short std::string lambda allocated on libstdc++, and on libc++ too, where it is 24 bytes and fits: a string’s copy is not noexcept. Microsoft’s STL kept both inside, but with its debug library (/MDd) the string lambda is 40 bytes and allocated on construction and again on copy.

Wherever the callable sits, a call goes through a pointer the object keeps unless the optimizer can see what was stored: at -O2, GCC 16.2 and Clang 23.1 on libstdc++ inlined a std::function built and called in the same function, but not one received as a const std::function& parameter. A template parameter’s call is a direct call the compiler can inline. The Core Guidelines’ T.49 asks you to avoid type erasure where possible, since it “incurs an extra level of indirection”, and names std::function as a case where it is appropriate.

A std::function owns a copy of its callable

Constructing a std::function copies or moves the callable into it, and copying the std::function copies the callable again, state and all:

[Example 7:
auto next_id = [id = 0]() mutable { return ++id; };

std::function<int()> first = next_id;  // a copy of the lambda
first();
first();
std::function<int()> second = first;  // a copy of that copy

std::printf("first %d\n", first());    // 3
std::printf("second %d\n", second());  // 3: its own counter
std::printf("next_id %d\n", next_id());  // 1: never called before

auto counter = [n = 0]() mutable { return ++n; };
std::function<int()> a = std::ref(counter);  // refers to counter
std::function<int()> b = a;  // still refers to counter
a();
b();
std::printf("counter %d\n", counter());  // 3: one shared counter

— end example]

All four printed what the comments say. Through std::ref, every copy shares one lambda, which must then outlive them all. The opposite trap is a lambda that captures a local by reference and is stored past that local’s life: the reference dangles. The Core Guidelines’ F.53 says to avoid capturing by reference in lambdas that will be used non-locally, including stored on the heap.

Move-only callables and const: std::move_only_function

std::function requires a copy-constructible target, so a lambda that owns a std::unique_ptr does not go in:

[Example 8:
auto factor = std::make_unique<double>(1.8);
std::function<double(double)> step =  // error: cannot be copied
    [f = std::move(factor)](double v) { return v * *f; };

— end example]

GCC names the rule: static assertion failed: std::function target must be copy-constructible. Microsoft’s STL says the same in its own static_assert; libc++ reports only a call to the lambda’s deleted copy constructor. C++23’s std::move_only_function holds it ([func.wrap.move]), and its signature can say const:

[Example 9:
auto scale = std::make_unique<Scale>(Scale{1.8});
std::move_only_function<double(double)> step =  // C++23
    [s = std::move(scale)](double v) { return (*s)(v); };
std::printf("%.1f\n", step(20.0));  // 36.0

auto owner = std::move(step);  // moved; a copy does not compile
std::printf("%.1f\n", owner(10.0));  // 18.0

std::move_only_function<int()> next_id = [id = 0]() mutable {
    return ++id;
};
next_id();
std::printf("next_id %d\n", next_id());  // 2

const std::move_only_function<double(double) const> convert =
    Scale{1.8};
std::printf("%.1f\n", convert(20.0));  // 36.0: const, callable

— end example]

MSVC, Clang 19.1 and GCC printed what the comments say, and a lambda capturing a unique_ptr went in without an allocation on Microsoft’s STL and libstdc++. Clang 23.1 stopped at the first use: libc++ 23.1 has no std::move_only_function. libstdc++ has it since GCC 12.1 and Microsoft’s STL since Visual Studio 2022 17.2 (Since When’s C++23 table).

The const closes a hole in std::function, whose call operator is const even when the target’s is not. A const std::function runs a mutable lambda and changes it:

[Example 10:
const std::function<int()> next_id = [id = 0]() mutable {
    return ++id;
};
std::printf("%d\n", next_id());  // 1
std::printf("%d\n", next_id());  // 2: a const object changed

— end example]

The same mutable lambda, three ways:

Case Compiles What happened
Calling a const std::function<int()> that holds it yes, as C++17 and C++23 printed 1, then 2
Calling a const std::move_only_function<int()> that holds it no its call operator is not const
Building a std::move_only_function<int() const> from it no a conversion error that does not say why

Clang’s note on the second case names the cause: 'this' argument has type 'const std::move_only_function<int ()>', but method is not marked const.

Two more differences: calling an empty std::move_only_function is a precondition violation, not an exception, and the signature can also say noexcept. C++26’s std::copyable_function brings the same signatures to a copyable wrapper; it is in libstdc++ only, since GCC 16.1 (Since When’s C++26 table).

Taking a callable as a parameter

A function that only calls its argument should take a template parameter, or auto since C++20: the call is direct and nothing is allocated. The same apply_all four ways:

[Example 11:
namespace by_template {
template <class F>
void apply_all(std::vector<double>& readings, F convert) {
    for (double& r : readings) r = convert(r);
}
}  // namespace by_template

namespace by_auto {
void apply_all(std::vector<double>& readings, auto convert) {  // C++20
    for (double& r : readings) r = convert(r);
}
}  // namespace by_auto

namespace by_pointer {
void apply_all(std::vector<double>& readings,
               double (*convert)(double)) {
    for (double& r : readings) r = convert(r);
}
}  // namespace by_pointer

namespace by_function {
void apply_all(std::vector<double>& readings,
               const std::function<double(double)>& convert) {
    for (double& r : readings) r = convert(r);
}
}  // namespace by_function

— end example]

Allocations per call, with [offset] capturing a double and [cal] copying a 32-byte Calibration:

Parameter, argument Microsoft’s STL libstdc++ libc++
Template or auto, [offset] 0 0 0
Template, [cal] 0 0 0
std::function, [offset] 0 0 0
std::function, [cal] 0 1 1

A std::function parameter builds a new std::function from the lambda at every call: ten calls with [cal] made ten allocations on libstdc++ and libc++. The pointer version took to_fahrenheit without allocating and rejected [offset] on all four. Writing the template is covered in C++ templates, and auto parameters in generic lambdas and function objects.

C++26’s std::function_ref is the parameter type made for this: it refers to the callable instead of owning a copy, and does not allocate ([func.wrap.ref]):

[Example 12:
void apply_all(std::vector<double>& readings,
               std::function_ref<double(double)> convert) {  // C++26
    for (double& r : readings) r = convert(r);
}

— end example]

It is in libstdc++ only, since GCC 16.1; GCC 16.2 built it and counted no allocation for the [cal] lambda. Microsoft’s STL and libc++ 23.1 do not have it (MSVC: C2039: 'function_ref': is not a member of 'std'); libc++’s C++26 status page lists it for LLVM 24. A function_ref must not outlive what it refers to: made from a temporary lambda, it dangles once the full expression ends, so keep it to parameters.

std::bind or a lambda

Use a lambda: std::bind(scale, _1, 1.8) and [](double v) { return scale(v, 1.8); } compute the same result, and the lambda says what it does. std::bind_front (C++20) fixes leading arguments without placeholders:

[Example 13:
using std::placeholders::_1;
auto by_bind = std::bind(scale, _1, 1.8);
auto by_lambda = [](double v) { return scale(v, 1.8); };
std::printf("%.1f %.1f\n", by_bind(20.0), by_lambda(20.0));  // 36.0

Sensor probe(21.5);
auto read = std::bind_front(&Sensor::scaled, &probe);  // C++20
std::printf("%.1f\n", read(2.0));  // 43.0

double factor = 1.8;
auto kept = std::bind(scale, _1, factor);  // a copy of factor
auto shared = std::bind(scale, _1, std::ref(factor));
factor = 2.0;
std::printf("%.1f %.1f\n", kept(10.0), shared(10.0));  // 18.0 20.0

— end example]

All four printed 36.0 36.0, 43.0 and 18.0 20.0: std::bind stores a copy of each bound argument, so the change to factor reached only the std::ref version ([func.bind.bind]). The bind result also accepts and ignores extra arguments: by_bind(20.0, 99, "ignored") printed 36.0 on all four, and the lambda cannot be called with two. std::bind_back (C++23) fixes the last arguments; all four rejected it as C++20 and built it as C++23.

clang-tidy’s modernize-avoid-bind flags each std::bind (prefer a lambda to std::bind), and version 19.1.5 offered a lambda with a forwarding auto&& parameter as the fix; how to run clang-tidy has the setup. Abseil’s Tip 108 gives the same advice: avoid std::bind, use a lambda or std::bind_front.

std::invoke: one way to call any callable

std::invoke(f, args...) (C++17) calls a function, a lambda, a member function on an object or through a pointer to one, or reads a data member, all with one syntax ([func.invoke]). It is what generic code writes instead of f(args...):

[Example 14:
auto add_offset = [offset](double v) { return v + offset; };
double a = std::invoke(to_fahrenheit, 20.0);         // 68.0
double b = std::invoke(add_offset, 20.0);            // 19.5
double c = std::invoke(&Sensor::value, probe);       // 21.5
double d = std::invoke(&Sensor::value, &probe);      // 21.5
double e = std::invoke(&Reading::celsius, reading);  // 19.0

— end example]

All four printed 68.0 19.5 21.5 21.5 19.0. C++23 adds std::invoke_r<R>, which converts the result to R.

Which to use

Job Use Since What it costs
Call it now, inside this function A template parameter, or auto before C++17; auto parameters C++20 No allocation; a direct call
A C API, a table of plain functions A function pointer before C++17 8 bytes; lambdas only with an empty capture list, []
Store any copyable callable std::function C++11 32 to 64 bytes, may allocate, usually an indirect call
Store a move-only callable, or make the call const std::move_only_function C++23; not in libc++ 23.1 Like std::function, never copied
A parameter you only call, without a template std::function_ref C++26; libstdc++ only, since GCC 16.1 No allocation; must not outlive the callable
Bind arguments A lambda, or std::bind_front C++11; bind_front C++20 The captures
Choose behavior at run time across a class hierarchy A virtual function before C++17 A call through the object’s vtable

More answers on templates and generic code are in Templates.

Questions

Is std::function the same as a function pointer?

No. A function pointer holds the address of a function and nothing else: 8 bytes on x86-64, no allocation, and it accepts only functions and lambdas with an empty capture list. A std::function holds a copy of any callable with its signature, captures included, is 32 to 64 bytes on x86-64 and may allocate. Called empty, it throws std::bad_function_call, where calling a null function pointer is undefined behavior.

Does std::function allocate memory?

It may: the standard only says implementations should avoid allocating for small callables. On x86-64, in the releases tested (MSVC 19.44, GCC 16.2, Clang 23.1 with libc++), Microsoft’s STL kept a callable inside the object up to 48 bytes if its move cannot throw, libstdc++ up to 16 bytes if it is trivially copyable, and libc++ (its default ABI) up to 24 bytes if its copy cannot throw. Anything else was allocated on the heap, and copying that std::function allocated again.

Can std::function store a lambda with captures or a member function?

Yes, both. A lambda with captures is copied into the std::function, captures and all, provided it is copy-constructible. A member function goes in a std::function whose first parameter is the object: std::function<double(const Sensor&)> read = &Sensor::value; is then called as read(probe).

Why can’t std::function hold a lambda that captures a unique_ptr?

The standard requires the target of a std::function to be copy-constructible, and a lambda that owns a std::unique_ptr is not. GCC 16.2 says so: static assertion failed: std::function target must be copy-constructible. C++23’s std::move_only_function holds it; libstdc++ has it since GCC 12.1 and Microsoft’s STL since Visual Studio 2022 17.2, while libc++ 23.1 does not have it.

Should I use std::bind or a lambda?

A lambda: [](double v) { return scale(v, 1.8); } says what std::bind(scale, _1, 1.8) hides behind a placeholder, and clang-tidy’s modernize-avoid-bind check flags each std::bind and offers a lambda in its place, except where one placeholder is used twice. To fix only the first arguments, C++20’s std::bind_front needs no placeholders; C++23’s std::bind_back fixes the last ones. All three copy their bound arguments unless you pass std::ref.

Is std::function slow?

No timing is given here, only what it does. A call usually goes through a pointer, where a template parameter’s call is direct and can be inlined, constructing or copying one may allocate, and the object is 32 to 64 bytes on x86-64. A function that only calls its argument avoids all three by taking a template parameter.

Sources

  1. std::functioncppreference.com
  2. [func.wrap.func] Class template functionC++ standard working draft
  3. [func.wrap.move] Move-only wrapperC++ standard working draft
  4. [func.wrap.ref] Non-owning wrapperC++ standard working draft
  5. [func.bind.bind] Function template bindC++ standard working draft
  6. T.49: Where possible, avoid type-erasureC++ Core Guidelines
  7. modernize-avoid-bindclang-tidy documentation
  8. Tip of the Week #108: Avoid std::bindAbseil

Read next

  1. C++ templates: how to write function and class templatesTemplates · Miriam Castellanos · written for C++17
  2. Strings as C++ template parameters with C++20 fixed_stringTemplates · Miriam Castellanos · written for C++20
  3. C++ generic lambdas, template lambdas and function objectsTemplates · Miriam Castellanos · written for C++20