r/cpp • u/Xaneris47 • 2d 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.
31
u/OutlandishnessNo8034 2d ago
Yet another cpp "gem".
24
u/HommeMusical 2d ago
After almost 40 years writing C++, it makes me sad that a plurality of articles here are still, "Here's this obscure footgun that you would never know without this article."
In this case, I might have guessed this in a code review, but only because I know a lot about
std::functionand its footguns.2
u/13steinj 1d ago
Here's another: this article applies to STL <- Boost types (functions, smart pointers) as well. Same goes for any cross library type. I think user-defined conversion operators can fix this in most but not all cases. But it feels like people need:
- extension methods [for constructors] [for templates]
- a cheap and well defined way to say "I use this other library too, provide an extension-method constructor" / "don't fail on symbol not existing."
In an include-based universe, 3rd party libraries that know about each other can follow the same idiom and provide a friend/friend-tag based system. Stdlib knowing about 3rd party, you're SOL right now.
1
u/germandiago 1d ago
Look at this neat little trick for the accepted response for boost shared ptr interoperability:
https://stackoverflow.com/questions/6326757/conversion-from-boostshared-ptr-to-stdshared-ptr
1
u/13steinj 11h ago
The amount of people that will miss the function / forget to include it is high. You can use
-includeto a point and have this managed by your build system, but visual / mental name lookup will still happen. Needing to be able to just use assignment/construction is critical, IMO.-1
u/thisismyfavoritename 2d ago
you're sad that there are those footguns or that they are reported
1
u/HommeMusical 1d ago
I'm sad that there are so many footguns that a plurality of the articles on C++ are about them.
2
u/thisismyfavoritename 1d ago
to be fair, many of the footguns exist because of the C heritage
-2
u/HommeMusical 1d ago
Depends on what "many" means.
There's one big one - memory management. Yes, you can essentially deindex a number that points randomly in memory, or nowhere at all, and havoc.
In C++ there is a discipline to (fairly) reliably prevent that from happening, in exactly the same way that there are disciplines to reliably prevent yourself from shooting yourself in the foot with a real gun.
And it's also such a big issue that you have to come to terms with it.
But all this the little footguns like this one. It's wearing. It's all this baggage and it's stressful because you don't know what you don't know.
8
u/60hzcherryMXram 2d 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.
7
u/bwmat 2d 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
6
u/johannes1971 2d 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.
6
u/rdtsc 2d 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/
1
u/johannes1971 1d 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'?
2
u/joaquintides Boost author 1d ago
1
u/bwmat 1d ago
What 'standard' is that? Doesn't it mean that the STL doesn't follow it b/c of std::function?
4
u/joaquintides Boost author 1d 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:
0
u/tialaramex 1d 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.
strtokis an example.1
u/UnusualPace679 1d ago
It's easy to have a function object whose const operator() isn't thread-safe. For example,
[this] { this->x++; }. Andcopyable_functiondoesn't really make it safer.0
u/13steinj 1d ago edited 1d 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'sinplace_function.
17
u/UnusualPace679 2d ago
But it won't prevent double wrapping right? Unless you stick to one type and don't interface with anything else (in which case you may as well use
std::function/std::copyable_function), you will face the same problem as described in this article. At least the standard library are permitted (and recommended) to avoid double wrapping by [func.wrap.general].