Cached at:
07/27/26, 07:44 AM
# Interconverting std::function with copyable_function
Source: [https://quuxplusone.github.io/blog/2026/07/26/function-explosion/](https://quuxplusone.github.io/blog/2026/07/26/function-explosion/)
C\+\+11 introduced`std::function`as a[type\-erased](https://quuxplusone.github.io/blog/2019/03/18/what-is-type-erasure/)callable\-object holder\.`function`was badly designed in a few ways: Its rarely used[“go fish” API](https://quuxplusone.github.io/blog/2019/03/27/design-space-for-std-function/#type-un-erasure)bloats every user; it advertises`operator\(\) const`even when the controlled object’s`operator\(\)`is mutating; it handles what should be a precondition violation by throwing an exception, which again bloats every user and means its`operator\(\)`can never be noexcept\. So C\+\+26 fixed these problems by adding[`std::copyable\_function`](https://en.cppreference.com/cpp/utility/functional/copyable_function)\. \(Which preserves*some*of`function`’s suboptimal design decisions:`copyable\_function<bool\(\)\>`remains implicitly convertible to`copyable\_function<void\(\)\>`, contextually convertible to`bool`, and assignable from`nullptr`\.\)
[P2548 “`copyable\_function`”](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2548r6.pdf)\(Hava 2023\) contains this guidance for implementors:
> It is recommended that implementors do not perform additional allocations when converting from a`copyable\_function`instantiation to a compatible`move\_only\_function`instantiation, but this is left as quality\-of\-implementation\.
Note that converting in the other direction — from`move\_only\_function`to`copyable\_function`— is disallowed:`copyable\_function`can hold only copyable callables, and`move\_only\_function`is not copyable\.
But`copyable\_function`is interconvertible — both directions — with`std::function`\! Both of these type\-erased wrappers are constructible from anything that is copyable and callable, and themselves*are*copyable and callable\. So we can do this:
```
std::function<int()> f = []{ return 5; };
std::copyable_function<int() const> cf = std::move(f);
std::function<int()> g = std::move(cf);
```
You’d hope the move from`f`into`cf`would be implemented something like this:

and vice versa for the move back into`g`:

But if`copyable\_function`doesn’t know about`function`, it might just treat`f`the same as any old rvalue of copyable, callable type: it might move a copy of`f`onto the heap to own\.

And vice versa for the move back into`g`:`function`might just treat`cf`the same as any old rvalue of copyable, callable type, and move a copy of`cf`onto the heap to own\.

This move operation is still relatively*performant*— it does only O\(1\) work for each construction — but after a sequence of \\\(n\\\) such constructions, we’ve produced a sort of “linked list” of callables of length \\\(n\\\)\. Each time we invoke`g`,`g`’s`operator\(\)`will call the`operator\(\)`of its controlled`copyable\_function`, which calls the`operator\(\)`of its controlled`function`, which calls the`operator\(\)`of its controlled lambda \(the green object in these diagrams\)\.
---
As of this writing \(July 2026\), libstdc\+\+ is the only one of the Big Three STL vendors to have implemented C\+\+26`copyable\_function`; and as of this writing, they’ve implemented the “bad” behavior diagrammed above\.
Consider the following contrived scenario: We have some kind of “handler callback” stored in a`std::function`\. Periodically we consider whether to modify that handler based on user input, e\.g\.
```
using Handler = std::function<bool(char)>;
Handler update(Handler h, bool negate) {
if (negate) {
h = [f = std::move(h)](char c) { return !f(c); };
}
return h;
}
~~~~
Handler h = original_handler;
while (~~~~) {
~~~~
h = update(std::move(h), (input == '!'));
}
```
Observe that`h`is moved into`update`, and moved out again \(thanks to[implicit move](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2266r3.html#wording)\)\. This uses the move constructor of`std::function`, which, like most move constructors, is quick and non\-allocating\.
But suppose one of our coworkers updates part of this code to C\+\+26, where we might reasonably prefer to use`std::copyable\_function`in place of`std::function`\.[\[1\]](https://quuxplusone.github.io/blog/2026/07/26/function-explosion/#footnote-should-you-abandon-func)If our coworker updates the whole codebase to use`copyable\_function`consistently, there’s no problem\. But what if they update only their part, so that their new`copyable\_function`\-based code must interoperate with everyone else’s old`function`\-based code? Then we get something like this:
```
using OldHandler = std::function<bool(char)>;
using NewHandler = std::copyable_function<bool(char)>;
OldHandler update(OldHandler h, bool negate);
// unchanged, for the benefit of pre-C++26 callers
~~~~
NewHandler h = original_handler;
while (~~~~) {
~~~~
h = update(std::move(h), (input == '!'));
}
```
This triggers that “linked list” scenario, and`h`’s call operator gets slower with every round\-trip through`update`— even when`negate`is`false`\!
I wrote up this little benchmark \([Godbolt](https://godbolt.org/z/zG9WW6bdK)\):
```
#include <chrono>
#include <cstdio>
#include <functional>
size_t now() {
static const auto epoch = std::chrono::high_resolution_clock::now();
auto tp = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::microseconds>(tp - epoch).count();
}
void print_elapsed_time(int i, size_t call_started) {
printf("Invocation on the %dth iteration took %zu us\n", i, now() - call_started);
}
using OldHandler = std::function<void(int, size_t)>;
using NewHandler = std::copyable_function<void(int, size_t) const>;
OldHandler update(OldHandler h, bool negate) {
if (negate) { /* do something here, but for this example we don't care what */ }
return h;
}
int main() {
NewHandler handler = print_elapsed_time;
for (int i = 0; i < 160'000; ++i) {
handler = update(std::move(handler), false);
if (i % 10'000 == 0) {
handler(i, now());
}
}
}
```
Run this program on libstdc\+\+ \(as of GCC 16\) and you’ll see output something like the following:
```
Invocation on the 0th iteration took 0 us
Invocation on the 10000th iteration took 180 us
Invocation on the 20000th iteration took 296 us
Invocation on the 30000th iteration took 365 us
[...]
Invocation on the 130000th iteration took 1057 us
Invocation on the 140000th iteration took 1108 us
Invocation on the 150000th iteration took 1202 us
```
Each time we invoke`handler\(i, now\(\)\)`the “linked list of callables” hanging off`handler`is 10,000 elements longer than before, and takes about 100 microseconds longer to chase those pointers\. Of course it also holds onto many kilobytes more memory than needed, too\. This is a “logical memory leak”: that memory is technically still accessible and will be freed by`h`’s destructor, but as long as`h`lives we’ll see our RAM usage creep higher and higher over time, without any change in the “logical” state of our program\.
---
As of July 2026, Microsoft STL makes`move\_only\_function`and`function`interoperate without such “logical memory leaks,” and I bet their`copyable\_function`will also avoid them, once they implement that type\. libstdc\+\+’s implementation may improve in the future\. Meanwhile, libc\+\+ hasn’t implemented either`move\_only\_function`or`copyable\_function`yet, so they still have plenty of time to think about this issue\.
---
Beware of a simpler version of this pitfall, by the way, when writing your own type\-erased types\. Make sure that when you copy a`Printable`\-holding\-an\-`int`, you get another`Printable`\-holding\-an\-`int`and not a`Printable`\-holding\-a\-`Printable`\-holding\-an\-`int`\. Write unit tests to make sure that still holds when constructing from a`Printable&`; from a`const Printable&`; from a`Printable&&`; and from a`const Printable&&`\. Solving this problem gets more painful if, like the STL, you have more than one type\-erased type that need to play well together\. Try to avoid getting into that situation if you can; but be aware of this pitfall if you must\.
---
Footnote:*Should*you abandon`function`for`copyable\_function`in C\+\+26? Some would say yes\. Personally I would say that you ought to abandon both, and write your own type\-erased callable instead\. It takes less than 100 lines\!
In completely green\-field C\+\+26 code, sure, I’d recommend`copyable\_function`over`function`\(once your vendor implements it\)\. I would*not*recommend mixing both types in the same brown\-field codebase; I’d pick one and stick with it, while working toward a point where you could update the entire codebase at once\.