From c99855721c5221dcd90606de98cd01b851dfdedc Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 25 Oct 2025 11:27:29 +0300 Subject: [PATCH 01/28] status quo coverage --- tests/std/test.lst | 1 + .../env.lst | 4 + .../test.cpp | 84 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 tests/std/tests/GH_005504_avoid_function_call_wrapping/env.lst create mode 100644 tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index 9dabf29d512..ed30e384ad4 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -270,6 +270,7 @@ tests\GH_005402_string_with_volatile_range tests\GH_005421_vector_algorithms_integer_class_type_iterator tests\GH_005472_do_not_overlap tests\GH_005546_containers_size_type_cast +tests\GH_005504_avoid_function_call_wrapping tests\GH_005553_regex_character_translation tests\GH_005768_pow_accuracy tests\LWG2381_num_get_floating_point diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/env.lst b/tests/std/tests/GH_005504_avoid_function_call_wrapping/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp new file mode 100644 index 00000000000..52ca0e18f55 --- /dev/null +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#pragma warning(disable : 4324) // 'large_callable': structure was padded due to alignment specifier + +using namespace std; + +struct copy_counter { + copy_counter() = default; + copy_counter(const copy_counter& other) : count(other.count + 1) {} + + int count = 0; +}; + +using function_type = int(copy_counter); + +struct small_callable { + int context = 42; + + int operator()(const copy_counter& counter) { + assert(context == 42); + return counter.count; + } +}; + +struct alignas(128) large_callable { + int context = 1729; + + int operator()(const copy_counter& counter) { + assert(context == 1729); + return counter.count; + } +}; + +int main() { + // Plain calls + { + function fn{small_callable{}}; + assert(fn(copy_counter{}) == 0); + } + { + function fn{large_callable{}}; + assert(fn(copy_counter{}) == 0); + } + { + move_only_function fn{small_callable{}}; + assert(fn(copy_counter{}) == 0); + } + { + move_only_function fn{large_callable{}}; + assert(fn(copy_counter{}) == 0); + } + + // Moves to the same + { + function fn{function{small_callable{}}}; + assert(fn(copy_counter{}) == 0); + } + { + function fn{function{large_callable{}}}; + assert(fn(copy_counter{}) == 0); + } + { + move_only_function fn{move_only_function{small_callable{}}}; + assert(fn(copy_counter{}) == 0); + } + { + move_only_function fn{move_only_function{large_callable{}}}; + assert(fn(copy_counter{}) == 0); + } + + // Moves from function to move_only_function + { + move_only_function fn{function{small_callable{}}}; + assert(fn(copy_counter{}) == 1); + } + { + move_only_function fn{function{large_callable{}}}; + assert(fn(copy_counter{}) == 1); + } +} From c1b8115220c0e52f510c217c3d82e7ca5ad786f5 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 25 Oct 2025 11:28:16 +0300 Subject: [PATCH 02/28] expected after the change --- .../std/tests/GH_005504_avoid_function_call_wrapping/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 52ca0e18f55..18e04551b50 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -75,10 +75,10 @@ int main() { // Moves from function to move_only_function { move_only_function fn{function{small_callable{}}}; - assert(fn(copy_counter{}) == 1); + assert(fn(copy_counter{}) == 0); } { move_only_function fn{function{large_callable{}}}; - assert(fn(copy_counter{}) == 1); + assert(fn(copy_counter{}) == 0); } } From a8114cdbc1a8ccde98c50e11c13d628fbcba1dca Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 25 Oct 2025 15:14:41 +0300 Subject: [PATCH 03/28] Do avoid one copying --- stl/inc/functional | 163 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 30 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 48feddd89c4..d6345320943 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -999,6 +999,25 @@ public: #endif // _MSVC_STL_DESTRUCTOR_TOMBSTONES } + bool _Local() const noexcept { // test for locally stored copy of object + return _Getimpl() == static_cast(&_Mystorage); + } + + _Ptrt* _Getimpl() const noexcept { // get pointer to object + return _Mystorage._Ptrs[_Small_object_num_ptrs - 1]; + } + + void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object + _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr; + } + + void _Tidy() noexcept { + if (!_Empty()) { // destroy callable object and maybe delete it + _Getimpl()->_Delete_this(!_Local()); + _Set(nullptr); + } + } + protected: template using _Enable_if_callable_t = enable_if_t, _Function>>, @@ -1066,13 +1085,6 @@ protected: } } - void _Tidy() noexcept { - if (!_Empty()) { // destroy callable object and maybe delete it - _Getimpl()->_Delete_this(!_Local()); - _Set(nullptr); - } - } - void _Swap(_Func_class& _Right) noexcept { // swap contents with contents of _Right if (!_Local() && !_Right._Local()) { // just swap pointers _Ptrt* _Temp = _Getimpl(); @@ -1097,10 +1109,6 @@ protected: #endif // _HAS_STATIC_RTTI private: - bool _Local() const noexcept { // test for locally stored copy of object - return _Getimpl() == static_cast(&_Mystorage); - } - union _Storage { // storage for small objects (basic_string is small) max_align_t _Dummy1; // for maximum alignment char _Dummy2[_Space_size]; // to permit aliasing @@ -1109,13 +1117,6 @@ private: _Storage _Mystorage; enum { _EEN_IMPL = _Small_object_num_ptrs - 1 }; // helper for expression evaluator - _Ptrt* _Getimpl() const noexcept { // get pointer to object - return _Mystorage._Ptrs[_Small_object_num_ptrs - 1]; - } - - void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object - _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr; - } }; template @@ -1423,6 +1424,12 @@ template _STL_UNREACHABLE; // no return value available for "continue on error" } +template +[[noreturn]] _Rx __stdcall _Function_old_not_callable(const _Move_only_function_data&, _Types&&...) + noexcept(_Noex) /* terminates if _Noex */ { + _Xbad_function_call(); +} + template _NODISCARD _Rx __stdcall _Function_inv_small(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) { if constexpr (is_void_v<_Rx>) { @@ -1441,6 +1448,18 @@ _NODISCARD _Rx __stdcall _Function_inv_large(const _Move_only_function_data& _Se } } +template +_NODISCARD _Rx __stdcall _Function_inv_old_large(const _Move_only_function_data& _Self, _Types&&... _Args) + noexcept(_Noex) { + return _Self._Large_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); +} + +template +_NODISCARD _Rx __stdcall _Function_inv_old_small(const _Move_only_function_data& _Self, _Types&&... _Args) + noexcept(_Noex) { + return _Self._Small_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); +} + template void __stdcall _Function_move_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept { const auto _Src_fn_ptr = _Src._Small_fn_ptr<_Vt>(); @@ -1458,6 +1477,14 @@ inline void __stdcall _Function_move_large(_Move_only_function_data& _Self, _Mov _CSTD memcpy(&_Self._Data, &_Src._Data, _Minimum_function_size); // Copy Impl* and functor data } +template +void __stdcall _Function_move_old_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept { + _Fn* const _Old_fn_impl = _Src._Small_fn_ptr<_Fn>(); + _Old_fn_impl->_Move(_Self._Buf_ptr()); + _Old_fn_impl->_Delete_this(false); + _Self._Impl = _Src._Impl; +} + template void __stdcall _Function_destroy_small(_Move_only_function_data& _Self) noexcept { _Self._Small_fn_ptr<_Vt>()->~_Vt(); @@ -1467,6 +1494,22 @@ inline void __stdcall _Function_deallocate_large_default_aligned(_Move_only_func ::operator delete(_Self._Large_fn_ptr()); } +template +inline void __stdcall _Function_destroy_old_large(_Move_only_function_data& _Self) noexcept { + _Self._Large_fn_ptr<_Fn>()->_Delete_this(true); +} + +template +inline void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _Self) noexcept { + _Fn* const _Old_fn_impl = _Self._Large_fn_ptr<_Fn>(); + _Old_fn_impl->_Delete_this(false); + ::operator delete(static_cast(_Old_fn_impl)); +} +template +inline void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { + _Self._Small_fn_ptr<_Fn>()->_Delete_this(false); +} + template void __stdcall _Function_deallocate_large_overaligned(_Move_only_function_data& _Self) noexcept { _STL_INTERNAL_STATIC_ASSERT(_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__); @@ -1564,6 +1607,14 @@ public: void(__stdcall* _Destroy)(_Move_only_function_data&) _NOEXCEPT_FNPTR; }; + enum class _Impl_kind { + _Usual, + _Old_fn_null, + _Old_fn_large, + _Old_fn_small_as_large, + _Old_fn_small, + }; + _Move_only_function_data _Data; _Move_only_function_base() noexcept = default; // leaves fields uninitialized @@ -1577,9 +1628,38 @@ public: _Data._Impl = nullptr; } + template + void _Construct_with_old_fn(_Fn&& _Func) { + const auto _Old_fn_impl = _Func._Getimpl(); + if (_Old_fn_impl == nullptr) { + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_null, _Vt, _VtInvQuals>(); + } else if (_Func._Local()) { + if constexpr (alignof(max_align_t) == alignof(void*)) { + // 64-bit target, can put small function into small move_only_function directly + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small, _Vt, _VtInvQuals>(); + _Old_fn_impl->_Move(_Data._Buf_ptr()); + _Func._Tidy(); + } else { + // 32-bit target, cannot small function into small move_only_function directly + // due to potentially not enough alignment. Allocate large function + void* _Where = ::operator new((_Small_object_num_ptrs - 1) * sizeof(void*)); + _Old_fn_impl->_Move(_Where); + _Func._Tidy(); + + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small_as_large, _Vt, _VtInvQuals>(); + _Data._Set_large_fn_ptr(_Where); + } + } else { + // Just take ownership of the inner impl pointer + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_large, _Vt, _VtInvQuals>(); + _Data._Set_large_fn_ptr(_Old_fn_impl); + _Func._Set(nullptr); + } + } + template void _Construct_with_fn(_CTypes&&... _Args) { - _Data._Impl = _Create_impl_ptr<_Vt, _VtInvQuals>(); + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Usual, _Vt, _VtInvQuals>(); if constexpr (_Large_function_engaged<_Vt>) { _Data._Set_large_fn_ptr(_STD _Function_new_large<_Vt>(_STD forward<_CTypes>(_Args)...)); } else { @@ -1670,10 +1750,29 @@ public: return _Ret ? _Ret : &_Null_move_only_function; } - template + template <_Impl_kind _Kind, class _Vt, class _VtInvQuals> _NODISCARD static constexpr _Impl_t _Create_impl() noexcept { _Impl_t _Impl{}; - if constexpr (_Large_function_engaged<_Vt>) { + if constexpr (_Kind != _Impl_kind::_Usual) { + using _Fn = remove_pointer_t()._Getimpl())>; + if constexpr (_Kind == _Impl_kind::_Old_fn_null) { + _Impl._Invoke = _Function_old_not_callable<_Rx, _Noexcept, _Types...>; + _Impl._Move = nullptr; + _Impl._Destroy = nullptr; + } else if constexpr (_Kind == _Impl_kind::_Old_fn_large) { + _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Noexcept, _Types...>; + _Impl._Move = nullptr; + _Impl._Destroy = _Function_destroy_old_large<_Fn>; + } else if constexpr (_Kind == _Impl_kind::_Old_fn_small_as_large) { + _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Noexcept, _Types...>; + _Impl._Move = nullptr; + _Impl._Destroy = _Function_destroy_old_small_as_large<_Fn>; + } else if constexpr (_Kind == _Impl_kind::_Old_fn_small) { + _Impl._Invoke = _Function_inv_old_small<_Fn, _Rx, _Noexcept, _Types...>; + _Impl._Move = _Function_move_old_small<_Fn>; + _Impl._Destroy = _Function_destroy_old_small<_Fn>; + } + } else if constexpr (_Large_function_engaged<_Vt>) { _Impl._Invoke = _Function_inv_large<_Vt, _VtInvQuals, _Rx, _Noexcept, _Types...>; _Impl._Move = nullptr; @@ -1708,9 +1807,9 @@ public: return _Impl; } - template + template <_Impl_kind _Kind, class _Vt, class _VtInvQuals> _NODISCARD static const _Impl_t* _Create_impl_ptr() noexcept { - static constexpr _Impl_t _Impl = _Create_impl<_Vt, _VtInvQuals>(); + static constexpr _Impl_t _Impl = _Create_impl<_Kind, _Vt, _VtInvQuals>(); return &_Impl; } }; @@ -1965,16 +2064,20 @@ public: using _Vt = decay_t<_Fn>; static_assert(is_constructible_v<_Vt, _Fn>, "_Vt should be constructible from _Fn. " "(N4950 [func.wrap.move.ctor]/6)"); + using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; - if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> || _Is_specialization_v<_Vt, move_only_function>) { - if (_Callable == nullptr) { - this->_Reset_to_null(); - return; + if constexpr (_Is_specialization_v<_Vt, function>) { + this->template _Construct_with_old_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); + } else { + if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> + || _Is_specialization_v<_Vt, move_only_function>) { + if (_Callable == nullptr) { + this->_Reset_to_null(); + return; + } } + this->template _Construct_with_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); } - - using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; - this->template _Construct_with_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); } template From 2521460bd82fa1068f7f5d46fdcc13624c59fc12 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 25 Oct 2025 18:49:26 +0300 Subject: [PATCH 04/28] sort better --- tests/std/test.lst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/test.lst b/tests/std/test.lst index ed30e384ad4..bfa67c4c471 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -269,8 +269,8 @@ tests\GH_005315_destructor_tombstones tests\GH_005402_string_with_volatile_range tests\GH_005421_vector_algorithms_integer_class_type_iterator tests\GH_005472_do_not_overlap -tests\GH_005546_containers_size_type_cast tests\GH_005504_avoid_function_call_wrapping +tests\GH_005546_containers_size_type_cast tests\GH_005553_regex_character_translation tests\GH_005768_pow_accuracy tests\LWG2381_num_get_floating_point From 7f62d36a68475035291ae83fac55d2fa2b2fd7d0 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 25 Oct 2025 18:50:16 +0300 Subject: [PATCH 05/28] template not inline --- stl/inc/functional | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index d6345320943..e752295dc56 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1495,18 +1495,18 @@ inline void __stdcall _Function_deallocate_large_default_aligned(_Move_only_func } template -inline void __stdcall _Function_destroy_old_large(_Move_only_function_data& _Self) noexcept { +void __stdcall _Function_destroy_old_large(_Move_only_function_data& _Self) noexcept { _Self._Large_fn_ptr<_Fn>()->_Delete_this(true); } template -inline void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _Self) noexcept { +void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _Self) noexcept { _Fn* const _Old_fn_impl = _Self._Large_fn_ptr<_Fn>(); _Old_fn_impl->_Delete_this(false); ::operator delete(static_cast(_Old_fn_impl)); } template -inline void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { +void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { _Self._Small_fn_ptr<_Fn>()->_Delete_this(false); } From 668f3adaa7d11348bec7ace12d4c88b46315d7aa Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 25 Oct 2025 19:32:27 +0300 Subject: [PATCH 06/28] exactly the sane signature --- stl/inc/functional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/functional b/stl/inc/functional index e752295dc56..237adc011da 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -2066,7 +2066,7 @@ public: "(N4950 [func.wrap.move.ctor]/6)"); using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; - if constexpr (_Is_specialization_v<_Vt, function>) { + if constexpr (is_same_v<_Vt, function<_Signature...>>) { this->template _Construct_with_old_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); } else { if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> From d317649a9cdfcd435dac8cde8301a902f1f61c99 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 26 Oct 2025 20:22:04 +0200 Subject: [PATCH 07/28] missing newline --- stl/inc/functional | 1 + 1 file changed, 1 insertion(+) diff --git a/stl/inc/functional b/stl/inc/functional index 237adc011da..106613c06c8 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1505,6 +1505,7 @@ void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _S _Old_fn_impl->_Delete_this(false); ::operator delete(static_cast(_Old_fn_impl)); } + template void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { _Self._Small_fn_ptr<_Fn>()->_Delete_this(false); From aa745c23a67a59e8bb1358d1f62eb9430d6b3cea Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 26 Oct 2025 21:54:38 +0200 Subject: [PATCH 08/28] =?UTF-8?q?Let's=20be=20friends=20=F0=9F=90=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stl/inc/functional | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 106613c06c8..63e92114cb6 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -999,25 +999,6 @@ public: #endif // _MSVC_STL_DESTRUCTOR_TOMBSTONES } - bool _Local() const noexcept { // test for locally stored copy of object - return _Getimpl() == static_cast(&_Mystorage); - } - - _Ptrt* _Getimpl() const noexcept { // get pointer to object - return _Mystorage._Ptrs[_Small_object_num_ptrs - 1]; - } - - void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object - _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr; - } - - void _Tidy() noexcept { - if (!_Empty()) { // destroy callable object and maybe delete it - _Getimpl()->_Delete_this(!_Local()); - _Set(nullptr); - } - } - protected: template using _Enable_if_callable_t = enable_if_t, _Function>>, @@ -1085,6 +1066,13 @@ protected: } } + void _Tidy() noexcept { + if (!_Empty()) { // destroy callable object and maybe delete it + _Getimpl()->_Delete_this(!_Local()); + _Set(nullptr); + } + } + void _Swap(_Func_class& _Right) noexcept { // swap contents with contents of _Right if (!_Local() && !_Right._Local()) { // just swap pointers _Ptrt* _Temp = _Getimpl(); @@ -1109,6 +1097,15 @@ protected: #endif // _HAS_STATIC_RTTI private: +#if _HAS_CXX23 + template + friend class _Move_only_function_base; +#endif // _HAS_CXX23 + + bool _Local() const noexcept { // test for locally stored copy of object + return _Getimpl() == static_cast(&_Mystorage); + } + union _Storage { // storage for small objects (basic_string is small) max_align_t _Dummy1; // for maximum alignment char _Dummy2[_Space_size]; // to permit aliasing @@ -1117,6 +1114,13 @@ private: _Storage _Mystorage; enum { _EEN_IMPL = _Small_object_num_ptrs - 1 }; // helper for expression evaluator + _Ptrt* _Getimpl() const noexcept { // get pointer to object + return _Mystorage._Ptrs[_Small_object_num_ptrs - 1]; + } + + void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object + _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr; + } }; template From 4e36558615033934e9c1223d676200a8290808dc Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Tue, 28 Oct 2025 22:06:33 +0200 Subject: [PATCH 09/28] selective friendship for now --- stl/inc/functional | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 63e92114cb6..57da0aff964 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -971,6 +971,11 @@ private: _Callable _Callee; }; +#if _HAS_CXX23 +template +class _Move_only_function_base; +#endif // _HAS_CXX23 + template class _Func_class : public _Arg_types<_Types...> { public: @@ -1098,8 +1103,7 @@ protected: private: #if _HAS_CXX23 - template - friend class _Move_only_function_base; + friend class _Move_only_function_base<_Ret, false, _Types...>; #endif // _HAS_CXX23 bool _Local() const noexcept { // test for locally stored copy of object From 6d8de9114747c96e206a3c65bd5d4f922eb2f24f Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Tue, 28 Oct 2025 22:16:02 +0200 Subject: [PATCH 10/28] yesexcept will add ths back with possible later changes when/if needed --- stl/inc/functional | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 57da0aff964..7b25350ceb4 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1432,9 +1432,8 @@ template _STL_UNREACHABLE; // no return value available for "continue on error" } -template -[[noreturn]] _Rx __stdcall _Function_old_not_callable(const _Move_only_function_data&, _Types&&...) - noexcept(_Noex) /* terminates if _Noex */ { +template +[[noreturn]] _Rx __stdcall _Function_old_not_callable(const _Move_only_function_data&, _Types&&...) { _Xbad_function_call(); } @@ -1456,15 +1455,13 @@ _NODISCARD _Rx __stdcall _Function_inv_large(const _Move_only_function_data& _Se } } -template -_NODISCARD _Rx __stdcall _Function_inv_old_large(const _Move_only_function_data& _Self, _Types&&... _Args) - noexcept(_Noex) { +template +_NODISCARD _Rx __stdcall _Function_inv_old_large(const _Move_only_function_data& _Self, _Types&&... _Args) { return _Self._Large_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); } -template -_NODISCARD _Rx __stdcall _Function_inv_old_small(const _Move_only_function_data& _Self, _Types&&... _Args) - noexcept(_Noex) { +template +_NODISCARD _Rx __stdcall _Function_inv_old_small(const _Move_only_function_data& _Self, _Types&&... _Args) { return _Self._Small_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); } @@ -1763,21 +1760,22 @@ public: _NODISCARD static constexpr _Impl_t _Create_impl() noexcept { _Impl_t _Impl{}; if constexpr (_Kind != _Impl_kind::_Usual) { + _STL_INTERNAL_STATIC_ASSERT(!_Noexcept); using _Fn = remove_pointer_t()._Getimpl())>; if constexpr (_Kind == _Impl_kind::_Old_fn_null) { - _Impl._Invoke = _Function_old_not_callable<_Rx, _Noexcept, _Types...>; + _Impl._Invoke = _Function_old_not_callable<_Rx, _Types...>; _Impl._Move = nullptr; _Impl._Destroy = nullptr; } else if constexpr (_Kind == _Impl_kind::_Old_fn_large) { - _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Noexcept, _Types...>; + _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; _Impl._Move = nullptr; _Impl._Destroy = _Function_destroy_old_large<_Fn>; } else if constexpr (_Kind == _Impl_kind::_Old_fn_small_as_large) { - _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Noexcept, _Types...>; + _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; _Impl._Move = nullptr; _Impl._Destroy = _Function_destroy_old_small_as_large<_Fn>; } else if constexpr (_Kind == _Impl_kind::_Old_fn_small) { - _Impl._Invoke = _Function_inv_old_small<_Fn, _Rx, _Noexcept, _Types...>; + _Impl._Invoke = _Function_inv_old_small<_Fn, _Rx, _Types...>; _Impl._Move = _Function_move_old_small<_Fn>; _Impl._Destroy = _Function_destroy_old_small<_Fn>; } From 544c670b2bb0c084a6e9bf13995d70c990313298 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 4 Nov 2025 12:16:13 -0800 Subject: [PATCH 11/28] Use extended `friend`. --- stl/inc/functional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/functional b/stl/inc/functional index 8daac18f2b9..31878a49506 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1103,7 +1103,7 @@ protected: private: #if _HAS_CXX23 - friend class _Move_only_function_base<_Ret, false, _Types...>; + friend _Move_only_function_base<_Ret, false, _Types...>; #endif // _HAS_CXX23 bool _Local() const noexcept { // test for locally stored copy of object From 207d8ef8f0e5f1863116fd26642a2750ceccff75 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 4 Nov 2025 12:33:24 -0800 Subject: [PATCH 12/28] Fix comments. --- stl/inc/functional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/functional b/stl/inc/functional index 31878a49506..37d96540238 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1637,7 +1637,7 @@ public: _Old_fn_impl->_Move(_Data._Buf_ptr()); _Func._Tidy(); } else { - // 32-bit target, cannot small function into small move_only_function directly + // 32-bit target, cannot put small function into small move_only_function directly // due to potentially not enough alignment. Allocate large function void* _Where = ::operator new((_Small_object_num_ptrs - 1) * sizeof(void*)); _Old_fn_impl->_Move(_Where); From ca4dd7db1233362c2e62a0af6d82c077bb01d550 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 4 Nov 2025 12:55:25 -0800 Subject: [PATCH 13/28] Clearly separate `_WIN64` codepaths. --- stl/inc/functional | 75 +++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 37d96540238..42c44ac55f1 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1460,10 +1460,12 @@ _NODISCARD _Rx __stdcall _Function_inv_old_large(const _Move_only_function_data& return _Self._Large_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); } +#ifdef _WIN64 template _NODISCARD _Rx __stdcall _Function_inv_old_small(const _Move_only_function_data& _Self, _Types&&... _Args) { return _Self._Small_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); } +#endif // ^^^ 64-bit ^^^ template void __stdcall _Function_move_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept { @@ -1482,6 +1484,7 @@ inline void __stdcall _Function_move_large(_Move_only_function_data& _Self, _Mov _CSTD memcpy(&_Self._Data, &_Src._Data, _Minimum_function_size); // Copy Impl* and functor data } +#ifdef _WIN64 template void __stdcall _Function_move_old_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept { _Fn* const _Old_fn_impl = _Src._Small_fn_ptr<_Fn>(); @@ -1489,6 +1492,7 @@ void __stdcall _Function_move_old_small(_Move_only_function_data& _Self, _Move_o _Old_fn_impl->_Delete_this(false); _Self._Impl = _Src._Impl; } +#endif // ^^^ 64-bit ^^^ template void __stdcall _Function_destroy_small(_Move_only_function_data& _Self) noexcept { @@ -1504,17 +1508,19 @@ void __stdcall _Function_destroy_old_large(_Move_only_function_data& _Self) noex _Self._Large_fn_ptr<_Fn>()->_Delete_this(true); } +#ifdef _WIN64 +template +void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { + _Self._Small_fn_ptr<_Fn>()->_Delete_this(false); +} +#else // ^^^ 64-bit / 32-bit vvv template void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _Self) noexcept { _Fn* const _Old_fn_impl = _Self._Large_fn_ptr<_Fn>(); _Old_fn_impl->_Delete_this(false); ::operator delete(static_cast(_Old_fn_impl)); } - -template -void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { - _Self._Small_fn_ptr<_Fn>()->_Delete_this(false); -} +#endif // ^^^ 32-bit ^^^ template void __stdcall _Function_deallocate_large_overaligned(_Move_only_function_data& _Self) noexcept { @@ -1605,11 +1611,14 @@ public: }; enum class _Impl_kind { - _Usual, - _Old_fn_null, - _Old_fn_large, - _Old_fn_small_as_large, - _Old_fn_small, + _Usual = 0, + _Old_fn_null = 1, + _Old_fn_large = 2, +#ifdef _WIN64 + _Old_fn_small = 3, +#else // ^^^ 64-bit / 32-bit vvv + _Old_fn_small_as_large = 4, +#endif // ^^^ 32-bit ^^^ }; _Move_only_function_data _Data; @@ -1631,21 +1640,23 @@ public: if (_Old_fn_impl == nullptr) { _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_null, _Vt, _VtInvQuals>(); } else if (_Func._Local()) { - if constexpr (alignof(max_align_t) == alignof(void*)) { - // 64-bit target, can put small function into small move_only_function directly - _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small, _Vt, _VtInvQuals>(); - _Old_fn_impl->_Move(_Data._Buf_ptr()); - _Func._Tidy(); - } else { - // 32-bit target, cannot put small function into small move_only_function directly - // due to potentially not enough alignment. Allocate large function - void* _Where = ::operator new((_Small_object_num_ptrs - 1) * sizeof(void*)); - _Old_fn_impl->_Move(_Where); - _Func._Tidy(); - - _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small_as_large, _Vt, _VtInvQuals>(); - _Data._Set_large_fn_ptr(_Where); - } +#ifdef _WIN64 + _STL_INTERNAL_STATIC_ASSERT(alignof(max_align_t) == alignof(void*)); + // 64-bit target, can put small function into small move_only_function directly + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small, _Vt, _VtInvQuals>(); + _Old_fn_impl->_Move(_Data._Buf_ptr()); + _Func._Tidy(); +#else // ^^^ 64-bit / 32-bit vvv + _STL_INTERNAL_STATIC_ASSERT(alignof(max_align_t) > alignof(void*)); + // 32-bit target, cannot put small function into small move_only_function directly + // due to potentially not enough alignment. Allocate large function + void* _Where = ::operator new((_Small_object_num_ptrs - 1) * sizeof(void*)); + _Old_fn_impl->_Move(_Where); + _Func._Tidy(); + + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small_as_large, _Vt, _VtInvQuals>(); + _Data._Set_large_fn_ptr(_Where); +#endif // ^^^ 32-bit ^^^ } else { // Just take ownership of the inner impl pointer _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_large, _Vt, _VtInvQuals>(); @@ -1761,14 +1772,18 @@ public: _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; _Impl._Move = nullptr; _Impl._Destroy = _Function_destroy_old_large<_Fn>; - } else if constexpr (_Kind == _Impl_kind::_Old_fn_small_as_large) { - _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; - _Impl._Move = nullptr; - _Impl._Destroy = _Function_destroy_old_small_as_large<_Fn>; - } else if constexpr (_Kind == _Impl_kind::_Old_fn_small) { + } else { +#ifdef _WIN64 + static_assert(_Kind == _Impl_kind::_Old_fn_small); _Impl._Invoke = _Function_inv_old_small<_Fn, _Rx, _Types...>; _Impl._Move = _Function_move_old_small<_Fn>; _Impl._Destroy = _Function_destroy_old_small<_Fn>; +#else // ^^^ 64-bit / 32-bit vvv + static_assert(_Kind == _Impl_kind::_Old_fn_small_as_large); + _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; + _Impl._Move = nullptr; + _Impl._Destroy = _Function_destroy_old_small_as_large<_Fn>; +#endif // ^^^ 32-bit ^^^ } } else if constexpr (_Large_function_engaged<_Vt>) { _Impl._Invoke = _Function_inv_large<_Vt, _VtInvQuals, _Rx, _Noexcept, _Types...>; From d45a91db8ffd250bc1b11b3ade3feae37e1a22cc Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 08:32:24 +0200 Subject: [PATCH 14/28] There's no invoke (yet) --- stl/inc/functional | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 42c44ac55f1..797e719660d 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1634,16 +1634,16 @@ public: _Data._Impl = nullptr; } - template + template void _Construct_with_old_fn(_Fn&& _Func) { const auto _Old_fn_impl = _Func._Getimpl(); if (_Old_fn_impl == nullptr) { - _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_null, _Vt, _VtInvQuals>(); + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_null, _Vt, void>(); } else if (_Func._Local()) { #ifdef _WIN64 _STL_INTERNAL_STATIC_ASSERT(alignof(max_align_t) == alignof(void*)); // 64-bit target, can put small function into small move_only_function directly - _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small, _Vt, _VtInvQuals>(); + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small, _Vt, void>(); _Old_fn_impl->_Move(_Data._Buf_ptr()); _Func._Tidy(); #else // ^^^ 64-bit / 32-bit vvv @@ -1654,12 +1654,12 @@ public: _Old_fn_impl->_Move(_Where); _Func._Tidy(); - _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small_as_large, _Vt, _VtInvQuals>(); + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small_as_large, _Vt, void>(); _Data._Set_large_fn_ptr(_Where); #endif // ^^^ 32-bit ^^^ } else { // Just take ownership of the inner impl pointer - _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_large, _Vt, _VtInvQuals>(); + _Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_large, _Vt, void>(); _Data._Set_large_fn_ptr(_Old_fn_impl); _Func._Set(nullptr); } @@ -1763,6 +1763,7 @@ public: _Impl_t _Impl{}; if constexpr (_Kind != _Impl_kind::_Usual) { _STL_INTERNAL_STATIC_ASSERT(!_Noexcept); + _STL_INTERNAL_STATIC_ASSERT(is_void_v<_VtInvQuals>); using _Fn = remove_pointer_t()._Getimpl())>; if constexpr (_Kind == _Impl_kind::_Old_fn_null) { _Impl._Invoke = _Function_old_not_callable<_Rx, _Types...>; @@ -2053,10 +2054,8 @@ public: using _Vt = decay_t<_Fn>; static_assert(is_constructible_v<_Vt, _Fn>, "_Vt should be constructible from _Fn. " "(N4950 [func.wrap.move.ctor]/6)"); - using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; - if constexpr (is_same_v<_Vt, function<_Signature...>>) { - this->template _Construct_with_old_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); + this->template _Construct_with_old_fn<_Vt>(_STD forward<_Fn>(_Callable)); } else { if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> || _Is_specialization_v<_Vt, move_only_function>) { @@ -2065,6 +2064,8 @@ public: return; } } + + using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; this->template _Construct_with_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); } } From 6ac3a87fc9a07fba2dd5ed750c0898a86e11ed73 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 09:01:34 +0200 Subject: [PATCH 15/28] Fuse small and large `_inv` to have fewer of them and mitigate possible future combinatorial explosion not fusing moves and destroys as they are distinct enough --- stl/inc/functional | 81 +++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 797e719660d..b8e87283b76 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1379,6 +1379,8 @@ _NODISCARD bool operator!=(nullptr_t, const function<_Fty>& _Other) noexcept { #endif // !_HAS_CXX20 #if _HAS_CXX23 +enum class _Function_storage_mode { _Small, _Large }; + // _Move_only_function_data is defined as an array of pointers. // The first element is always a pointer to _Move_only_function_base::_Impl_t; it emulates a vtable pointer. // The other pointers are used as storage for a small functor; @@ -1401,16 +1403,15 @@ union alignas(max_align_t) _Move_only_function_data { return &_Data + _Buf_offset<_Fn>; } - template - _NODISCARD _Fn* _Small_fn_ptr() const noexcept { - // cast away const to avoid complication of const propagation to here; - // const correctness is still enforced by _Move_only_function_call specializations. - return static_cast<_Fn*>(const_cast<_Move_only_function_data*>(this)->_Buf_ptr<_Fn>()); - } - - template - _NODISCARD _Fn* _Large_fn_ptr() const noexcept { - return static_cast<_Fn*>(_Pointers[1]); + template + _NODISCARD _Fn* _Fn_ptr() const noexcept { + if constexpr (_Mode == _Function_storage_mode::_Small) { + // cast away const to avoid complication of const propagation to here; + // const correctness is still enforced by _Move_only_function_call specializations. + return static_cast<_Fn*>(const_cast<_Move_only_function_data*>(this)->_Buf_ptr<_Fn>()); + } else { + return static_cast<_Fn*>(_Pointers[1]); + } } void _Set_large_fn_ptr(void* const _Value) noexcept { @@ -1437,39 +1438,23 @@ template _Xbad_function_call(); } -template -_NODISCARD _Rx __stdcall _Function_inv_small(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) { - if constexpr (is_void_v<_Rx>) { - (void) _STD invoke(static_cast<_VtInvQuals>(*_Self._Small_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...); - } else { - return _STD invoke(static_cast<_VtInvQuals>(*_Self._Small_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...); - } -} - -template -_NODISCARD _Rx __stdcall _Function_inv_large(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) { +template +_NODISCARD _Rx __stdcall _Function_inv(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) { if constexpr (is_void_v<_Rx>) { - (void) _STD invoke(static_cast<_VtInvQuals>(*_Self._Large_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...); + (void) _STD invoke(static_cast<_VtInvQuals>(*_Self._Fn_ptr<_Vt, _Mode>()), _STD forward<_Types>(_Args)...); } else { - return _STD invoke(static_cast<_VtInvQuals>(*_Self._Large_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...); + return _STD invoke(static_cast<_VtInvQuals>(*_Self._Fn_ptr<_Vt, _Mode>()), _STD forward<_Types>(_Args)...); } } -template -_NODISCARD _Rx __stdcall _Function_inv_old_large(const _Move_only_function_data& _Self, _Types&&... _Args) { - return _Self._Large_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); +template +_NODISCARD _Rx __stdcall _Function_inv_old(const _Move_only_function_data& _Self, _Types&&... _Args) { + return _Self._Fn_ptr<_Fn, _Mode>()->_Do_call(_STD forward<_Types>(_Args)...); } -#ifdef _WIN64 -template -_NODISCARD _Rx __stdcall _Function_inv_old_small(const _Move_only_function_data& _Self, _Types&&... _Args) { - return _Self._Small_fn_ptr<_Fn>()->_Do_call(_STD forward<_Types>(_Args)...); -} -#endif // ^^^ 64-bit ^^^ - template void __stdcall _Function_move_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept { - const auto _Src_fn_ptr = _Src._Small_fn_ptr<_Vt>(); + const auto _Src_fn_ptr = _Src._Fn_ptr<_Vt, _Function_storage_mode::_Small>(); ::new (_Self._Buf_ptr<_Vt>()) _Vt(_STD move(*_Src_fn_ptr)); _Src_fn_ptr->~_Vt(); _Self._Impl = _Src._Impl; @@ -1487,7 +1472,7 @@ inline void __stdcall _Function_move_large(_Move_only_function_data& _Self, _Mov #ifdef _WIN64 template void __stdcall _Function_move_old_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept { - _Fn* const _Old_fn_impl = _Src._Small_fn_ptr<_Fn>(); + _Fn* const _Old_fn_impl = _Src._Fn_ptr<_Fn, _Function_storage_mode::_Small>(); _Old_fn_impl->_Move(_Self._Buf_ptr()); _Old_fn_impl->_Delete_this(false); _Self._Impl = _Src._Impl; @@ -1496,27 +1481,27 @@ void __stdcall _Function_move_old_small(_Move_only_function_data& _Self, _Move_o template void __stdcall _Function_destroy_small(_Move_only_function_data& _Self) noexcept { - _Self._Small_fn_ptr<_Vt>()->~_Vt(); + _Self._Fn_ptr<_Vt, _Function_storage_mode::_Small>()->~_Vt(); } inline void __stdcall _Function_deallocate_large_default_aligned(_Move_only_function_data& _Self) noexcept { - ::operator delete(_Self._Large_fn_ptr()); + ::operator delete(_Self._Fn_ptr()); } template void __stdcall _Function_destroy_old_large(_Move_only_function_data& _Self) noexcept { - _Self._Large_fn_ptr<_Fn>()->_Delete_this(true); + _Self._Fn_ptr<_Fn, _Function_storage_mode::_Large>()->_Delete_this(true); } #ifdef _WIN64 template void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept { - _Self._Small_fn_ptr<_Fn>()->_Delete_this(false); + _Self._Fn_ptr<_Fn, _Function_storage_mode::_Small>()->_Delete_this(false); } #else // ^^^ 64-bit / 32-bit vvv template void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _Self) noexcept { - _Fn* const _Old_fn_impl = _Self._Large_fn_ptr<_Fn>(); + _Fn* const _Old_fn_impl = _Self._Fn_ptr<_Fn, _Function_storage_mode::_Large>(); _Old_fn_impl->_Delete_this(false); ::operator delete(static_cast(_Old_fn_impl)); } @@ -1526,15 +1511,15 @@ template void __stdcall _Function_deallocate_large_overaligned(_Move_only_function_data& _Self) noexcept { _STL_INTERNAL_STATIC_ASSERT(_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__); #ifdef __cpp_aligned_new - ::operator delete(_Self._Large_fn_ptr(), align_val_t{_Align}); + ::operator delete(_Self._Fn_ptr(), align_val_t{_Align}); #else // ^^^ defined(__cpp_aligned_new) / !defined(__cpp_aligned_new) vvv - ::operator delete(_Self._Large_fn_ptr()); + ::operator delete(_Self._Fn_ptr()); #endif // ^^^ !defined(__cpp_aligned_new) ^^^ } template void __stdcall _Function_destroy_large(_Move_only_function_data& _Self) noexcept { - const auto _Pfn = _Self._Large_fn_ptr<_Vt>(); + const auto _Pfn = _Self._Fn_ptr<_Vt, _Function_storage_mode::_Large>(); _Pfn->~_Vt(); #ifdef __cpp_aligned_new if constexpr (alignof(_Vt) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { @@ -1770,24 +1755,24 @@ public: _Impl._Move = nullptr; _Impl._Destroy = nullptr; } else if constexpr (_Kind == _Impl_kind::_Old_fn_large) { - _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; + _Impl._Invoke = _Function_inv_old<_Fn, _Function_storage_mode::_Large, _Rx, _Types...>; _Impl._Move = nullptr; _Impl._Destroy = _Function_destroy_old_large<_Fn>; } else { #ifdef _WIN64 static_assert(_Kind == _Impl_kind::_Old_fn_small); - _Impl._Invoke = _Function_inv_old_small<_Fn, _Rx, _Types...>; + _Impl._Invoke = _Function_inv_old<_Fn, _Function_storage_mode::_Small, _Rx, _Types...>; _Impl._Move = _Function_move_old_small<_Fn>; _Impl._Destroy = _Function_destroy_old_small<_Fn>; #else // ^^^ 64-bit / 32-bit vvv static_assert(_Kind == _Impl_kind::_Old_fn_small_as_large); - _Impl._Invoke = _Function_inv_old_large<_Fn, _Rx, _Types...>; + _Impl._Invoke = _Function_inv_old<_Fn, _Function_storage_mode::_Large, _Rx, _Types...>; _Impl._Move = nullptr; _Impl._Destroy = _Function_destroy_old_small_as_large<_Fn>; #endif // ^^^ 32-bit ^^^ } } else if constexpr (_Large_function_engaged<_Vt>) { - _Impl._Invoke = _Function_inv_large<_Vt, _VtInvQuals, _Rx, _Noexcept, _Types...>; + _Impl._Invoke = _Function_inv<_Vt, _VtInvQuals, _Function_storage_mode::_Large, _Rx, _Noexcept, _Types...>; _Impl._Move = nullptr; if constexpr (is_trivially_destructible_v<_Vt>) { @@ -1800,7 +1785,7 @@ public: _Impl._Destroy = _Function_destroy_large<_Vt>; } } else { - _Impl._Invoke = _Function_inv_small<_Vt, _VtInvQuals, _Rx, _Noexcept, _Types...>; + _Impl._Invoke = _Function_inv<_Vt, _VtInvQuals, _Function_storage_mode::_Small, _Rx, _Noexcept, _Types...>; if constexpr (is_trivially_copyable_v<_Vt> && is_trivially_destructible_v<_Vt>) { if constexpr ((_Function_small_copy_size<_Vt>) > _Minimum_function_size) { From 889cc4e463a5d8d2493ad257f22f079f64240aca Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 21:26:45 +0200 Subject: [PATCH 16/28] organize tests --- .../test.cpp | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 18e04551b50..8573c5e3dca 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -35,50 +35,34 @@ struct alignas(128) large_callable { } }; +template +void test_plain_call(int expected_copies) { + Wrapper fn{Callable{}}; + assert(fn(copy_counter{}) == expected_copies); +} + +template +void test_wrapped_call(int expected_copies) { + InnerWrapper inner{Callable{}}; + OuterWrapper outer{std::move(inner)}; + assert(!inner); + assert(outer(copy_counter{}) == expected_copies); +} + int main() { // Plain calls - { - function fn{small_callable{}}; - assert(fn(copy_counter{}) == 0); - } - { - function fn{large_callable{}}; - assert(fn(copy_counter{}) == 0); - } - { - move_only_function fn{small_callable{}}; - assert(fn(copy_counter{}) == 0); - } - { - move_only_function fn{large_callable{}}; - assert(fn(copy_counter{}) == 0); - } + test_plain_call, small_callable>(0); + test_plain_call, large_callable>(0); + test_plain_call, small_callable>(0); + test_plain_call, large_callable>(0); // Moves to the same - { - function fn{function{small_callable{}}}; - assert(fn(copy_counter{}) == 0); - } - { - function fn{function{large_callable{}}}; - assert(fn(copy_counter{}) == 0); - } - { - move_only_function fn{move_only_function{small_callable{}}}; - assert(fn(copy_counter{}) == 0); - } - { - move_only_function fn{move_only_function{large_callable{}}}; - assert(fn(copy_counter{}) == 0); - } + test_wrapped_call, function, small_callable>(0); + test_wrapped_call, function, large_callable>(0); + test_wrapped_call, move_only_function, small_callable>(0); + test_wrapped_call, move_only_function, large_callable>(0); // Moves from function to move_only_function - { - move_only_function fn{function{small_callable{}}}; - assert(fn(copy_counter{}) == 0); - } - { - move_only_function fn{function{large_callable{}}}; - assert(fn(copy_counter{}) == 0); - } + test_wrapped_call, function, small_callable>(0); + test_wrapped_call, function, large_callable>(0); } From 131bd44fb63c266e680901a360361c0bd6d90ab7 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 21:36:02 +0200 Subject: [PATCH 17/28] nulls --- .../test.cpp | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 8573c5e3dca..76164c1b9d0 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -49,6 +49,38 @@ void test_wrapped_call(int expected_copies) { assert(outer(copy_counter{}) == expected_copies); } +template +void test_plain_null(bool throws) { + Wrapper fn{}; + assert(!fn); + + if (throws) { + try { + fn(copy_counter{}); + abort(); // should not reach + } catch (bad_function_call&){ + } + } +} + +template +void test_wrapped_call(bool outer_is_null, bool outer_throws) { + InnerWrapper inner{}; + OuterWrapper outer{std::move(inner)}; + assert(!inner); + assert(!outer == outer_is_null); + + if (outer_throws) { + try { + outer(copy_counter{}); + abort(); // should not reach + } catch (bad_function_call&) { + } + } else { + // UB that in our implementation tries to call doom function; we do not test that + } +} + int main() { // Plain calls test_plain_call, small_callable>(0); @@ -65,4 +97,13 @@ int main() { // Moves from function to move_only_function test_wrapped_call, function, small_callable>(0); test_wrapped_call, function, large_callable>(0); + + // nulls + test_plain_null>(true); + test_plain_null>(false); + + // wrapped nulls + test_wrapped_call, function>(true, true); + test_wrapped_call, move_only_function>(true, false); + test_wrapped_call, function>(false, true); } From 3f66c0bdcb52a82d94078cc3512028566ee01a43 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 21:37:24 +0200 Subject: [PATCH 18/28] alignment check --- tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 76164c1b9d0..16db5b52ba0 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -30,6 +30,7 @@ struct alignas(128) large_callable { int context = 1729; int operator()(const copy_counter& counter) { + assert((reinterpret_cast(this) & 0x7f) == 0); assert(context == 1729); return counter.count; } From 20c683f9c0b50869953690a429fdf87859db28e6 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 22:05:34 +0200 Subject: [PATCH 19/28] count allocations --- .../test.cpp | 92 +++++++++++++++---- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 16db5b52ba0..1b2e2eba2f1 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -3,10 +3,66 @@ #include #include +#include + +using namespace std; #pragma warning(disable : 4324) // 'large_callable': structure was padded due to alignment specifier +#pragma warning(disable : 28251) // Inconsistent annotation for 'new': this instance has no annotations. -using namespace std; +int alloc_count = 0; +int dealloc_count = 0; + +void* operator new(size_t size) { + if (size == 0) { + size = 1; + } + + ++alloc_count; + void* result = malloc(size); + if (!result) { + throw std::bad_alloc{}; + } + return result; +} + +void operator delete(void* mem) noexcept { + ++dealloc_count; + free(mem); +} + +void* operator new(size_t size, align_val_t al) { + if (size == 0) { + size = 1; + } + + ++alloc_count; + void* result = _aligned_malloc(size, static_cast(al)); + if (!result) { + throw std::bad_alloc{}; + } + return result; +} + +void operator delete(void* mem, align_val_t) noexcept { + ++dealloc_count; + _aligned_free(mem); +} + + +struct alloc_checker { + explicit alloc_checker(int expected_delta_) : expected_delta(expected_delta_) {} + alloc_checker(const alloc_checker&) = delete; + alloc_checker& operator=(const alloc_checker&) = delete; + + ~alloc_checker() { + assert(alloc_count - before == expected_delta); + assert(alloc_count == dealloc_count); + } + + int expected_delta; + int before = alloc_count; +}; struct copy_counter { copy_counter() = default; @@ -15,7 +71,7 @@ struct copy_counter { int count = 0; }; -using function_type = int(copy_counter); +using fn_type = int(copy_counter); struct small_callable { int context = 42; @@ -36,7 +92,7 @@ struct alignas(128) large_callable { } }; -template +template void test_plain_call(int expected_copies) { Wrapper fn{Callable{}}; assert(fn(copy_counter{}) == expected_copies); @@ -84,27 +140,27 @@ void test_wrapped_call(bool outer_is_null, bool outer_throws) { int main() { // Plain calls - test_plain_call, small_callable>(0); - test_plain_call, large_callable>(0); - test_plain_call, small_callable>(0); - test_plain_call, large_callable>(0); + alloc_checker(0), test_plain_call, small_callable>(0); + alloc_checker(1), test_plain_call, large_callable>(0); + alloc_checker(0), test_plain_call, small_callable>(0); + alloc_checker(1), test_plain_call, large_callable>(0); // Moves to the same - test_wrapped_call, function, small_callable>(0); - test_wrapped_call, function, large_callable>(0); - test_wrapped_call, move_only_function, small_callable>(0); - test_wrapped_call, move_only_function, large_callable>(0); + alloc_checker(0), test_wrapped_call, function, small_callable>(0); + alloc_checker(1), test_wrapped_call, function, large_callable>(0); + alloc_checker(0), test_wrapped_call, move_only_function, small_callable>(0); + alloc_checker(1), test_wrapped_call, move_only_function, large_callable>(0); // Moves from function to move_only_function - test_wrapped_call, function, small_callable>(0); - test_wrapped_call, function, large_callable>(0); + alloc_checker(0), test_wrapped_call, function, small_callable>(0); + alloc_checker(1), test_wrapped_call, function, large_callable>(0); // nulls - test_plain_null>(true); - test_plain_null>(false); + alloc_checker(0), test_plain_null>(true); + alloc_checker(0), test_plain_null>(false); // wrapped nulls - test_wrapped_call, function>(true, true); - test_wrapped_call, move_only_function>(true, false); - test_wrapped_call, function>(false, true); + alloc_checker(0), test_wrapped_call, function>(true, true); + alloc_checker(0), test_wrapped_call, move_only_function>(true, false); + alloc_checker(0), test_wrapped_call, function>(false, true); } From f71a0d20bccea30d241bfbb280c6375f1b3cc428 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 22:09:39 +0200 Subject: [PATCH 20/28] Win32 --- .../tests/GH_005504_avoid_function_call_wrapping/test.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 1b2e2eba2f1..89f12fcae92 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -152,7 +152,12 @@ int main() { alloc_checker(1), test_wrapped_call, move_only_function, large_callable>(0); // Moves from function to move_only_function - alloc_checker(0), test_wrapped_call, function, small_callable>(0); +#ifdef _WIN64 + alloc_checker(0), +#else + alloc_checker(1), +#endif + test_wrapped_call, function, small_callable>(0); alloc_checker(1), test_wrapped_call, function, large_callable>(0); // nulls From 1b2a9c67491158cce8e7e70c9b7cfb01b4df80f8 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 5 Nov 2025 22:15:31 +0200 Subject: [PATCH 21/28] format --- .../tests/GH_005504_avoid_function_call_wrapping/test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 89f12fcae92..27c635e6b0a 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -10,7 +10,7 @@ using namespace std; #pragma warning(disable : 4324) // 'large_callable': structure was padded due to alignment specifier #pragma warning(disable : 28251) // Inconsistent annotation for 'new': this instance has no annotations. -int alloc_count = 0; +int alloc_count = 0; int dealloc_count = 0; void* operator new(size_t size) { @@ -52,7 +52,7 @@ void operator delete(void* mem, align_val_t) noexcept { struct alloc_checker { explicit alloc_checker(int expected_delta_) : expected_delta(expected_delta_) {} - alloc_checker(const alloc_checker&) = delete; + alloc_checker(const alloc_checker&) = delete; alloc_checker& operator=(const alloc_checker&) = delete; ~alloc_checker() { @@ -115,7 +115,7 @@ void test_plain_null(bool throws) { try { fn(copy_counter{}); abort(); // should not reach - } catch (bad_function_call&){ + } catch (bad_function_call&) { } } } @@ -126,7 +126,7 @@ void test_wrapped_call(bool outer_is_null, bool outer_throws) { OuterWrapper outer{std::move(inner)}; assert(!inner); assert(!outer == outer_is_null); - + if (outer_throws) { try { outer(copy_counter{}); From 194d7ba54260a7d77a2ade3c4c78c10e4a1d316e Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 6 Nov 2025 20:10:51 +0200 Subject: [PATCH 22/28] tests cleanup --- .../test.cpp | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 27c635e6b0a..328c23be045 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -13,35 +13,30 @@ using namespace std; int alloc_count = 0; int dealloc_count = 0; -void* operator new(size_t size) { - if (size == 0) { - size = 1; - } +size_t adjust_alloc_size(size_t size) { + return size != 0 ? size : 1; +} - ++alloc_count; - void* result = malloc(size); +void* check_alloc(void* result) { if (!result) { throw std::bad_alloc{}; } return result; } +void* operator new(size_t size) { + ++alloc_count; + return check_alloc(malloc(adjust_alloc_size(size))); +} + void operator delete(void* mem) noexcept { ++dealloc_count; free(mem); } void* operator new(size_t size, align_val_t al) { - if (size == 0) { - size = 1; - } - ++alloc_count; - void* result = _aligned_malloc(size, static_cast(al)); - if (!result) { - throw std::bad_alloc{}; - } - return result; + return check_alloc(_aligned_malloc(adjust_alloc_size(size), static_cast(al))); } void operator delete(void* mem, align_val_t) noexcept { @@ -49,9 +44,8 @@ void operator delete(void* mem, align_val_t) noexcept { _aligned_free(mem); } - struct alloc_checker { - explicit alloc_checker(int expected_delta_) : expected_delta(expected_delta_) {} + explicit alloc_checker(const int expected_delta_) : expected_delta(expected_delta_) {} alloc_checker(const alloc_checker&) = delete; alloc_checker& operator=(const alloc_checker&) = delete; @@ -60,8 +54,8 @@ struct alloc_checker { assert(alloc_count == dealloc_count); } - int expected_delta; - int before = alloc_count; + const int expected_delta; + const int before = alloc_count; }; struct copy_counter { @@ -74,7 +68,7 @@ struct copy_counter { using fn_type = int(copy_counter); struct small_callable { - int context = 42; + const int context = 42; int operator()(const copy_counter& counter) { assert(context == 42); @@ -83,7 +77,7 @@ struct small_callable { }; struct alignas(128) large_callable { - int context = 1729; + const int context = 1729; int operator()(const copy_counter& counter) { assert((reinterpret_cast(this) & 0x7f) == 0); @@ -93,13 +87,13 @@ struct alignas(128) large_callable { }; template -void test_plain_call(int expected_copies) { +void test_plain_call(const int expected_copies) { Wrapper fn{Callable{}}; assert(fn(copy_counter{}) == expected_copies); } template -void test_wrapped_call(int expected_copies) { +void test_wrapped_call(const int expected_copies) { InnerWrapper inner{Callable{}}; OuterWrapper outer{std::move(inner)}; assert(!inner); @@ -107,35 +101,32 @@ void test_wrapped_call(int expected_copies) { } template -void test_plain_null(bool throws) { - Wrapper fn{}; - assert(!fn); - +void check_call_null(Wrapper& wrapper, const bool throws) { if (throws) { try { - fn(copy_counter{}); + wrapper(copy_counter{}); abort(); // should not reach } catch (bad_function_call&) { } + } else { + // UB that in our implementation tries to call doom function; we do not test that } } +template +void test_plain_null(const bool throws) { + Wrapper fn{}; + assert(!fn); + check_call_null(fn, throws); +} + template -void test_wrapped_call(bool outer_is_null, bool outer_throws) { +void test_wrapped_call(const bool outer_is_null, const bool outer_throws) { InnerWrapper inner{}; OuterWrapper outer{std::move(inner)}; assert(!inner); assert(!outer == outer_is_null); - - if (outer_throws) { - try { - outer(copy_counter{}); - abort(); // should not reach - } catch (bad_function_call&) { - } - } else { - // UB that in our implementation tries to call doom function; we do not test that - } + check_call_null(outer, outer_throws); } int main() { From aa8e4daf82c7dcb253d4dcb182a361a0ab2aaf66 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 6 Nov 2025 22:30:08 +0200 Subject: [PATCH 23/28] even more const --- .../GH_005504_avoid_function_call_wrapping/test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 328c23be045..c36a25aaa54 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -13,33 +13,33 @@ using namespace std; int alloc_count = 0; int dealloc_count = 0; -size_t adjust_alloc_size(size_t size) { +size_t adjust_alloc_size(const size_t size) { return size != 0 ? size : 1; } -void* check_alloc(void* result) { +void* check_alloc(void* const result) { if (!result) { throw std::bad_alloc{}; } return result; } -void* operator new(size_t size) { +void* operator new(const size_t size) { ++alloc_count; return check_alloc(malloc(adjust_alloc_size(size))); } -void operator delete(void* mem) noexcept { +void operator delete(void* const mem) noexcept { ++dealloc_count; free(mem); } -void* operator new(size_t size, align_val_t al) { +void* operator new(const size_t size, const align_val_t al) { ++alloc_count; return check_alloc(_aligned_malloc(adjust_alloc_size(size), static_cast(al))); } -void operator delete(void* mem, align_val_t) noexcept { +void operator delete(void* const mem, align_val_t) noexcept { ++dealloc_count; _aligned_free(mem); } From 0a88f380872994a92294f6d20c3e3004074f3a2c Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 6 Nov 2025 22:31:26 +0200 Subject: [PATCH 24/28] no std:: --- .../tests/GH_005504_avoid_function_call_wrapping/test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index c36a25aaa54..5f035282ac7 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -19,7 +19,7 @@ size_t adjust_alloc_size(const size_t size) { void* check_alloc(void* const result) { if (!result) { - throw std::bad_alloc{}; + throw bad_alloc{}; } return result; } @@ -95,7 +95,7 @@ void test_plain_call(const int expected_copies) { template void test_wrapped_call(const int expected_copies) { InnerWrapper inner{Callable{}}; - OuterWrapper outer{std::move(inner)}; + OuterWrapper outer{move(inner)}; assert(!inner); assert(outer(copy_counter{}) == expected_copies); } @@ -123,7 +123,7 @@ void test_plain_null(const bool throws) { template void test_wrapped_call(const bool outer_is_null, const bool outer_throws) { InnerWrapper inner{}; - OuterWrapper outer{std::move(inner)}; + OuterWrapper outer{move(inner)}; assert(!inner); assert(!outer == outer_is_null); check_call_null(outer, outer_throws); From a90f9a0e0a22f432ce1e63561800baa0dddf7155 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 11 Nov 2025 08:55:29 -0800 Subject: [PATCH 25/28] Include more headers. --- .../std/tests/GH_005504_avoid_function_call_wrapping/test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 5f035282ac7..7d7191b41b3 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -2,8 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include +#include +#include #include #include +#include using namespace std; From d5c0f2ccec5c14a2c946263fdecfd902509509f7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 11 Nov 2025 09:09:25 -0800 Subject: [PATCH 26/28] abort() => assert(false), catch by const ref. --- .../std/tests/GH_005504_avoid_function_call_wrapping/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 7d7191b41b3..1a2863bc97e 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -109,8 +109,8 @@ void check_call_null(Wrapper& wrapper, const bool throws) { if (throws) { try { wrapper(copy_counter{}); - abort(); // should not reach - } catch (bad_function_call&) { + assert(false); // should not reach + } catch (const bad_function_call&) { } } else { // UB that in our implementation tries to call doom function; we do not test that From 1d30d812f80b254275f4240c00de8b6ba373e90f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 11 Nov 2025 09:09:51 -0800 Subject: [PATCH 27/28] Construct alloc_checker temporaries with braces. --- .../test.cpp | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 1a2863bc97e..6b5688c53fd 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -135,32 +135,32 @@ void test_wrapped_call(const bool outer_is_null, const bool outer_throws) { int main() { // Plain calls - alloc_checker(0), test_plain_call, small_callable>(0); - alloc_checker(1), test_plain_call, large_callable>(0); - alloc_checker(0), test_plain_call, small_callable>(0); - alloc_checker(1), test_plain_call, large_callable>(0); + alloc_checker{0}, test_plain_call, small_callable>(0); + alloc_checker{1}, test_plain_call, large_callable>(0); + alloc_checker{0}, test_plain_call, small_callable>(0); + alloc_checker{1}, test_plain_call, large_callable>(0); // Moves to the same - alloc_checker(0), test_wrapped_call, function, small_callable>(0); - alloc_checker(1), test_wrapped_call, function, large_callable>(0); - alloc_checker(0), test_wrapped_call, move_only_function, small_callable>(0); - alloc_checker(1), test_wrapped_call, move_only_function, large_callable>(0); + alloc_checker{0}, test_wrapped_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); + alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); // Moves from function to move_only_function #ifdef _WIN64 - alloc_checker(0), + alloc_checker{0}, #else - alloc_checker(1), + alloc_checker{1}, #endif test_wrapped_call, function, small_callable>(0); - alloc_checker(1), test_wrapped_call, function, large_callable>(0); + alloc_checker{1}, test_wrapped_call, function, large_callable>(0); // nulls - alloc_checker(0), test_plain_null>(true); - alloc_checker(0), test_plain_null>(false); + alloc_checker{0}, test_plain_null>(true); + alloc_checker{0}, test_plain_null>(false); // wrapped nulls - alloc_checker(0), test_wrapped_call, function>(true, true); - alloc_checker(0), test_wrapped_call, move_only_function>(true, false); - alloc_checker(0), test_wrapped_call, function>(false, true); + alloc_checker{0}, test_wrapped_call, function>(true, true); + alloc_checker{0}, test_wrapped_call, move_only_function>(true, false); + alloc_checker{0}, test_wrapped_call, function>(false, true); } From e2f67cf8bddab987adf8337e7227213e9fa02507 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 11 Nov 2025 09:12:38 -0800 Subject: [PATCH 28/28] test_wrapped_call (2 args) => test_wrapped_null --- .../tests/GH_005504_avoid_function_call_wrapping/test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 6b5688c53fd..44ec403e220 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -125,7 +125,7 @@ void test_plain_null(const bool throws) { } template -void test_wrapped_call(const bool outer_is_null, const bool outer_throws) { +void test_wrapped_null(const bool outer_is_null, const bool outer_throws) { InnerWrapper inner{}; OuterWrapper outer{move(inner)}; assert(!inner); @@ -160,7 +160,7 @@ int main() { alloc_checker{0}, test_plain_null>(false); // wrapped nulls - alloc_checker{0}, test_wrapped_call, function>(true, true); - alloc_checker{0}, test_wrapped_call, move_only_function>(true, false); - alloc_checker{0}, test_wrapped_call, function>(false, true); + alloc_checker{0}, test_wrapped_null, function>(true, true); + alloc_checker{0}, test_wrapped_null, move_only_function>(true, false); + alloc_checker{0}, test_wrapped_null, function>(false, true); }