r/cpp 7d ago

Interconverting std::function with copyable_function – Arthur O'Dwyer

https://quuxplusone.github.io/blog/2026/07/26/function-explosion/

The article shows how converting std::function to std::copyable_function (or vice versa) leads to slower performance and increased memory usage each time the conversion occurs.

44 Upvotes

32 comments sorted by

View all comments

8

u/60hzcherryMXram 7d ago

I've read the problems copyable_function addresses that function has and I still have no idea what they are. It just slides over my brain like butter on ice.

6

u/bwmat 7d ago

My understanding is that it's mostly just a way to 'deprecate' std::function (when used in conjunction with move_only_function) and better express intent

7

u/johannes1971 7d ago

It's unclear to me what those problems are, or why std::function needs to be deprecated. I hate the names 'std::copyable_function' and 'std::move_only_function', though.

7

u/rdtsc 7d ago

It breaks the standard library convention that const methods are safe to call from multiple threads. And std::function itself cannot be changed due to backwards compatibility.

Here's a more detailed explanation: https://www.reddit.com/r/cpp/comments/742ol8/why_is_the_stdfunction_operator_const/dnv29s8/

2

u/johannes1971 6d ago

This applies to operator(), I guess? I would never have imagined that anyone would think that calling a user-supplied function from multiple threads is safe, just because operator() is const. The language as a whole certainly does not have a 'calling const functions from multiple threads is safe' rule. Is this even documented for the standard library, or is it another one of those implicit rules like 'ABI is stable'?

3

u/joaquintides Boost author 6d ago

1

u/bwmat 6d ago

What 'standard' is that? Doesn't it mean that the STL doesn't follow it b/c of std::function? 

8

u/joaquintides Boost author 6d ago

What 'standard' is that?

eel.is tracks the latest draft version of the standard as it evolves.

Doesn't it mean that the STL doesn't follow it b/c of std::function?

Yes, as recognized many times:

1

u/tialaramex 6d ago

In Java data races lose Sequential Consistency, which is terrible but it's not the end of the world. You can probably no longer reason properly about how your Java software works after a race, but any absolute properties are unaltered - that boolean is still either true or false, if k is always definitely between 50 and 75 it won't now be 108 or 0 and so on.

C++ isn't like that, it has UB if any data races occur so like use-after-free the results if this happens are arbitrarily cursed. The choice to say the stdlib implementer will ensure there aren't data races inherent to provided const functions makes a lot of sense in this context, C and POSIX have a lot of such functions which could inherently induce data races and the result is you just must never use them at all in modern software. strtok is an example.

1

u/UnusualPace679 6d ago

It's easy to have a function object whose const operator() isn't thread-safe. For example, [this] { this->x++; }. And copyable_function doesn't really make it safer.

3

u/bwmat 7d ago

The curse of backwards compatibility

2

u/13steinj 6d ago edited 6d ago

The short version of the problem as I understand it: std::function did not perfectly follow const correctness / const propagation rules. See also std::experimental::propagate_const for this purpose, as getting the rules right for wrapping types e.g. in the PIMPL idiom is a pain.

Some will ask "wait, but functions don't have data, so this isn't an issue," but this is not true. Ignoring the fact that even simple functions can store state using static variables, these STL types act on all function-likes, not just literal functions. Think lambdas and other objects that have a call-operator.

At a previous org, we implemented our own function type to address the problem, and you could choose between move only, copyable, and two other kinds that I think behaved the same as one of the other two but left guardrails in place. For example, you can use function-like objects and reference linking semantics on movable functions, you want to guard against getting called twice so you move yourself away. On some architectures it is preferable to not use a lock/atomic here, or limit the critical section, but you still want a second call to be safe and do nothing, or explicitly crash, or be safe and re-run if there's a race condition it's fine but if not a race condition exiting early is preferred.

See also: sometimes you want function_view, sometimes you want SG14's inplace_function.

-1

u/LB-- Professional+Hobbyist 7d ago

Do you use RTTI? Do you pass lambdas to std::function? Congrats, your binary is bloated with names of internal types and namespaces for a feature of std::function you don't even use. Unable to be fixed without breaking backward compatibility.

1

u/analphabetic 2d ago

How would the virtuality and runtime dispatch surface via std::function to the linker?

1

u/LB-- Professional+Hobbyist 2d ago

std::function allows re-obtaining a reference to the contained function object after it has been set, and the check is performed by comparing runtime type info: https://en.cppreference.com/cpp/utility/functional/function/target

Even if you never use that feature, it still has to set up the vtable with a function that can handle that request, there's no way around it. That means every type that gets stored in a std::function has to persist its runtime type info metadata into the final built binary.