Interconverting Std:Function with Copyable_function

jandeboevrie1 pts0 comments

Interconverting `std::function` with `copyable_function` – Arthur O'Dwyer – Stuff mostly about C++

Interconverting std::function with copyable_function

C++11 introduced std::function as a type-erased<br>callable-object holder. function was badly designed in a few ways: Its rarely used<br>“go fish” API bloats<br>every user; it advertises operator() const even when the controlled object’s operator()<br>is mutating; it handles what should be a precondition violation by throwing an exception,<br>which again bloats every user and means its operator() can never be noexcept.<br>So C++26 fixed these problems by adding<br>std::copyable_function.<br>(Which preserves some of function’s<br>suboptimal design decisions: copyable_function remains implicitly convertible<br>to copyable_function, contextually convertible to bool, and assignable<br>from nullptr.)

P2548 “copyable_function” (Hava 2023)<br>contains this guidance for implementors:

It is recommended that implementors do not perform additional allocations when<br>converting from a copyable_function instantiation to a compatible<br>move_only_function instantiation, but this is left as quality-of-implementation.

Note that converting in the other direction — from move_only_function to<br>copyable_function — is disallowed: copyable_function can hold only<br>copyable callables, and move_only_function is not copyable.

But copyable_function is interconvertible — both directions — with std::function!<br>Both of these type-erased wrappers are constructible from anything that is copyable<br>and callable, and themselves are copyable and callable. So we can do this:

std::function f = []{ return 5; };<br>std::copyable_function cf = std::move(f);<br>std::function 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<br>the same as any old rvalue of copyable, callable type: it might move a copy of f<br>onto the heap to own.

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

This move operation is still relatively performant — it does only O(1) work<br>for each construction — but after a sequence of \(n\) such constructions, we’ve<br>produced a sort of “linked list” of callables of length \(n\). Each time we invoke<br>g, g’s operator() will call the operator() of its controlled copyable_function,<br>which calls the operator() of its controlled function, which calls the operator()<br>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<br>to have implemented C++26 copyable_function; and as of this writing, they’ve<br>implemented the “bad” behavior diagrammed above.

Consider the following contrived scenario:<br>We have some kind of “handler callback” stored in a std::function. Periodically we<br>consider whether to modify that handler based on user input, e.g.

using Handler = std::function;

Handler update(Handler h, bool negate) {<br>if (negate) {<br>h = [f = std::move(h)](char c) { return !f(c); };<br>return h;

~~~~<br>Handler h = original_handler;<br>while (~~~~) {<br>~~~~<br>h = update(std::move(h), (input == '!'));

Observe that h is moved into update, and moved out again (thanks to<br>implicit move).<br>This uses the move constructor of std::function, which, like most move constructors,<br>is quick and non-allocating.

But suppose one of our coworkers updates part of this code to C++26,<br>where we might reasonably prefer to use std::copyable_function in place of<br>std::function.[1]<br>If our coworker updates the whole codebase to use copyable_function consistently,<br>there’s no problem. But what if they update only their part, so that their new<br>copyable_function-based code must interoperate with everyone else’s old<br>function-based code? Then we get something like this:

using OldHandler = std::function;<br>using NewHandler = std::copyable_function;

OldHandler update(OldHandler h, bool negate);<br>// unchanged, for the benefit of pre-C++26 callers

~~~~<br>NewHandler h = original_handler;<br>while (~~~~) {<br>~~~~<br>h = update(std::move(h), (input == '!'));

This triggers that “linked list” scenario, and h’s call operator gets slower<br>with every round-trip through update — even when negate is false!

I wrote up this little benchmark (Godbolt):

#include<br>#include<br>#include

size_t now() {<br>static const auto epoch = std::chrono::high_resolution_clock::now();<br>auto tp = std::chrono::high_resolution_clock::now();<br>return std::chrono::duration_cast(tp - epoch).count();

void print_elapsed_time(int i, size_t call_started) {<br>printf("Invocation on the %dth iteration took %zu us\n", i, now() - call_started);

using OldHandler = std::function;<br>using NewHandler = std::copyable_function;

OldHandler update(OldHandler h, bool negate) {<br>if (negate) { /* do something here, but for this example we don't care what */ }<br>return h;

int main() {<br>NewHandler handler...

copyable_function function move operator update handler

Related Articles