Prepare move_only_function machinery for future C++26 interactions by AlexGuteniev · Pull Request #5849 · microsoft/STL (original) (raw)

Towards #5504. Towards #3803.

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:

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:

Ultimately, the thunk elimination is not very big win, comparing to extra invoke elimination. And we can eliminate extra invoke even without unifying thunks.