[libcxx-commits] [PATCH] D130631: [libc++] Implement P0288R9 (move_only_function)
Adrian Vogelsgesang via Phabricator via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Jul 29 20:23:02 PDT 2022
avogelsgesang added a comment.
Thanks for looking into this :) `std::move_only_function` is very high on the list of my favorite new C++23 features!
================
Comment at: libcxx/include/__functional/move_only_function_impl.h:280
+ _LIBCPP_HIDE_FROM_ABI move_only_function(move_only_function&& __other) noexcept : __storage_(__other.__storage_) {
+ __other.__storage_ = {};
+ }
----------------
Is it more efficient to only set `__status_ = NotEngaged`? Afaict, this would overwrite only 1 instead of 48 bytes
(here and a couple of other places which set `__storage_ = {}`
================
Comment at: libcxx/include/__functional/move_only_function_impl.h:294
+ __storage_ = {};
+ return;
+ } else {
----------------
redundant `return` before `else`?
================
Comment at: libcxx/include/__functional/move_only_function_impl.h:360
+ auto __call = [&](auto&& __callable, auto&& __data) {
+ return std::bit_cast<void (*)(_LIBCPP_MOVE_ONLY_FUNCTION_CV void*, _ArgTypes...)>(__callable)(
+ __data, std::forward<_ArgTypes>(__args)...);
----------------
`std::bit_cast<__ReturnT (*)(...)>` instead of `std::bit_cast<void (*)(...)>`?
Also, it seems there are no test cases which instantiate `move_only_function` with a return type != `void`?
================
Comment at: libcxx/include/__functional/move_only_function_impl.h:360
+ auto __call = [&](auto&& __callable, auto&& __data) {
+ return std::bit_cast<void (*)(_LIBCPP_MOVE_ONLY_FUNCTION_CV void*, _ArgTypes...)>(__callable)(
+ __data, std::forward<_ArgTypes>(__args)...);
----------------
avogelsgesang wrote:
> `std::bit_cast<__ReturnT (*)(...)>` instead of `std::bit_cast<void (*)(...)>`?
>
> Also, it seems there are no test cases which instantiate `move_only_function` with a return type != `void`?
I think this should also take `_LIBCPP_MOVE_ONLY_FUNCTION_NOEXCEPT` into account when casting to the function pointer.
As written now, for `move_only_function<void () noexcept>`, this `operator()` will contain a call to a non-noexcept function. The compiler will hence generate additional exception handling code. Also, this blocks tail call optimization (https://godbolt.org/z/39bn95cdT)
================
Comment at: libcxx/include/__functional/move_only_function_impl.h:365
+ using enum _Store::_Status;
+ switch (__storage_.__large_.__status_) {
+ case _NotEngaged:
----------------
I think this can be implemented more efficiently by folding this switch with the indirect function call we are doing here anyway.
**Proposed changes**
Move this switch into `__function_wrappers_impl::__call`, taking `_Store::_Status` as a template parameter:
```
template<_Store::_Status status>
_LIBCPP_HIDE_FROM_ABI static _ReturnT
__call(_Store* store, _ArgTypes&&... __args) noexcept(_LIBCPP_MOVE_ONLY_FUNCTION_NOEXCEPT) {
auto __doCall = [&](void* __functor) {
return std::invoke(static_cast<_Functor _LIBCPP_MOVE_ONLY_FUNCTION_INV_QUALS>(*static_cast<_Functor*>(__functor)),
std::forward<_ArgTypes>(__args)...);
}
using enum _Store::_Status;
if constexpr (status == _NotEngaged) {
_LIBCPP_ASSERT(false, "Tried to call move_only_function which doesn't hold a callable object");
} else if constexpr (status == _TriviallyDestructible) {
return __doCall(&__storage_.__trivially_destructible_.__data_);
} else if constexpr (status == _TriviallyRelocatable) {
return __doCall(&__storage_.__trivially_relocatable_.__data_);
} else if constexpr (status == _Heap) {
return __doCall(&__storage_.__large_.__data_);
}
}
```
In `__construct`, the `__storage.__large_.__call` would then be assigned similar to
```
auto& __data = __storage_.__large_;
__data.__call_ = std::bit_cast<void*>(&_FuncWraps::__call<_Store::_Status::_Heap>);
```
Because the `__call_` member is always at the same position inside the `__move_only_function_storage`, we can then implement `operator()` as a simple
```
_LIBCPP_HIDE_FROM_ABI _ReturnT operator()(_ArgTypes... __args) _LIBCPP_MOVE_ONLY_FUNCTION_CV_REF
noexcept(_LIBCPP_MOVE_ONLY_FUNCTION_NOEXCEPT) {
return std::bit_cast<ReturnT (*)(_LIBCPP_MOVE_ONLY_FUNCTION_CV _Store*, _ArgTypes...)>(__storage_.__large_.__call_)(
&__storage, std::forward<_ArgTypes>(__args)...);
```
**Why I think this is a good idea**
Replacing the `switch` with a `if constexpr` chain guarantees that this switch is completely optimized away.
Futhermore for usages like
```
void setCallback(std::move_only_function<void()> cb);
void setCallbackWithGreetings(bool bye, const char* whom) {
takesSomeCallback([=]() {
std::cout << (bye ? "Goodbye " : "Hello ") << whom << "\n";
});
}
```
the compiler can inline the lambda body into the `__function_wrappers_impl::__call`. The compiler can then fold the offset computation for `__storage_.__large_.__data_` with the offset computations for `whom` and `bye`. With the previous implementation of `operator()` this would not have been possible since the `__storage_.__large_.__data_` happens before the indirect call, while the `whom` & `bye` offset computations happened inside the indirect call.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D130631/new/
https://reviews.llvm.org/D130631
More information about the libcxx-commits
mailing list