Prepare move_only_function machinery for future C++26 interactions by AlexGuteniev · Pull Request #5849 · microsoft/STL (original) (raw)
Will help sharing the implementation with copyable_function implementation sharing and interaction with function_ref.
Also a minor enhancement on its own.
Steps
Rename to drop _Move_only part
It would be silly to derive copyable_function from _Move_only_function_call.
Ditto for packaged_task from _Move_only_function_base and function from _Move_only_function_call. in vNext.
Avoid saying fields for the data member
This is not directly related to future integrations.
Just prioritizing proper C++ terminology over OOP terminology.
Recognition that OOP is far from being universal, and call wrappers are not necessarily implemented with OOP.
Using void object pointer
⚠️ We may want to revert this part, while keeping the other parts, see details the here ⚠️
This is helpful for function_ref<type>{move_only_function<type>{...}} and function_ref<type>{copyable_function<type>{...}} call unwrapping. Specifically, bound-entity exposition-only member can be of void object pointer type, then the type of _Invoke matches the type of thunk-ptr in function_ref, so we can easily do call unwrapping with thunk elimination.
@frederick-vs-ja has pointed out in #5504 (comment) that the unwrapping involving function_ref is LWG-4264, and the unwrapping with outer function_ref has unexpected effects. I've clarified this on email with Tomasz Kaminski. The idea is that in the case of:
move_only_function<R(Args...)> f1{...};
function_ref<R(Args...)> f2{f1};
will not track the changes of f1 value in case of double indirection avoidance, so looks unacceptable, but for this:
function_ref<R(Args...)> f1{...};
function_ref<R(Args...) noexcept> f2{f1};
not tracking the changes of f1 may be acceptable, as this imitates a copy of function_ref,
For the reverse unwrapping, that is move_only_function<type>{function_ref<type>{...}} or copyable_function<type>{function_ref<type>{...}}, this change is not enough. I think there are no good options to do the call unwrapping with thunk elimination, will discuss that below. This reverse unwrapping is not problematic semantically though, as the heavy wrappers are supposed to copy/move, and hopefully LWG-4264 will enable it.
Use void object pointer to a non-constant
We have to pass both constant and non-constant objects through the same thunk. For the constant ones the operator() is const. Due to having small functions, we cannot utilize shallow-but-not-deep constant, and have to put const_cast somewhere. This is not a problem for const correctness, as all calls are checked with is_invokable... family, and we don't do anything besides the forwarding and the invoke in the call operator.
Prior to this change, the object was passed as constant, the const_cast was on the thunk side.
The change makes the passed object pointer non-constant, and moves the const_cast to the operator() side.
This makes bound-entity simply void* in case the above part is kept.
Quasi-ABI-break
We can break ABI, but we actually don't do this here if you think of it:
- Despite the mass renaming, the layout hasn't actually changed
- There's risk for names to match older or newer names, but I don't think we ever use these names again
- Not all references are pointers, but reference to a large structure will always be implemented as an object pointer
- Any object pointers, constant or not, are the same on non-imaginary hardware
Still I want to merge this before ABI changes closure to be extra safe
What's up with unwrapping of inner function_ref
This is about move_only_function<type>{function_ref<type>{...}} or copyable_function<type>{function_ref<type>{...}} unwrapping with thunk elimination.
TL;DR: The win is small and there's no way to make it without penalizing more common use cases.
Whereas the current move_only_function thunks adjust pointer to the beginning of the object internally to find the real callable, the straightforward function_ref implementation would need to pass the direct pointer to a callable, because there's nothing much of function_ref.
The extra thunk not only means extra code, but also extra pointer saved inside move_only_function holding function_ref. Still this looks like the best options.
There are ways to unify move_only_function and function_ref thunk by:
- Always store direct pointer in
move_only_functionas well. Will make smallmove_only_functionthunk the same as large, and make small only function self-referencing, that is containing a pointer inside its own data. Will spend extra pointer. Will cause the revert of avoid allocations part from Unwrap call of function passed to move_only_function #5808, as that will not fit even in x64 - Have the direct pointer computed in
move_only_functioncall. Penalizes the code size and run time of normalmove_only_functionusage - Adjust the pointer in
function_ref. Penalizes the code size and run time of normalfunction_refusage
Ultimately, the thunk elimination is not very big win, comparing to extra invoke elimination. And we can eliminate extra invoke even without unifying thunks.