From 91cc7476ee2045c0a4f5d3d87fa6577248ea7b2b Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 16:31:31 +0200 Subject: [PATCH 01/12] avoid preprocessor to make the checker less messy --- .../tests/GH_005504_avoid_function_call_wrapping/test.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 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 44ec403e22..d3ffb48b7b 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 @@ -146,12 +146,10 @@ int main() { alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); + constexpr bool is_64_bit = sizeof(void*) > 4; + // Moves from function to move_only_function -#ifdef _WIN64 - alloc_checker{0}, -#else - alloc_checker{1}, -#endif + alloc_checker{is_64_bit ? 0 : 1}, test_wrapped_call, function, small_callable>(0); alloc_checker{1}, test_wrapped_call, function, large_callable>(0); From 12d458b13cfa0d5e1c8911a174acdad6a453c558 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 16:33:11 +0200 Subject: [PATCH 02/12] test status quo on abominables and noexcept specifier --- .../test.cpp | 40 ++++++++++++++++++- 1 file changed, 38 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 d3ffb48b7b..a2df15bcb1 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 @@ -70,11 +70,18 @@ struct copy_counter { }; using fn_type = int(copy_counter); +using fn_type_r = int(copy_counter) &; +using fn_type_c = int(copy_counter) const; + +#ifdef __cpp_noexcept_function_type +using fn_type_nx = int(copy_counter) noexcept; +#endif // defined(__cpp_noexcept_function_type) + struct small_callable { const int context = 42; - int operator()(const copy_counter& counter) { + int operator()(const copy_counter& counter) const noexcept { assert(context == 42); return counter.count; } @@ -83,7 +90,7 @@ struct small_callable { struct alignas(128) large_callable { const int context = 1729; - int operator()(const copy_counter& counter) { + int operator()(const copy_counter& counter) const noexcept { assert((reinterpret_cast(this) & 0x7f) == 0); assert(context == 1729); return counter.count; @@ -146,6 +153,25 @@ int main() { alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); + // Abominables and noexcept specifier + alloc_checker{1}, test_wrapped_call, move_only_function, small_callable>(1); + alloc_checker{2}, test_wrapped_call, move_only_function, large_callable>(1); + alloc_checker{1}, test_wrapped_call, move_only_function, small_callable>(1); + alloc_checker{2}, test_wrapped_call, move_only_function, large_callable>(1); + + static_assert(!is_constructible_v, move_only_function>); + static_assert(!is_constructible_v, move_only_function>); + static_assert(!is_constructible_v, move_only_function>); + static_assert(!is_constructible_v, move_only_function>); + +#ifdef __cpp_noexcept_function_type + alloc_checker{1}, test_wrapped_call, move_only_function, small_callable>(1); + alloc_checker{2}, test_wrapped_call, move_only_function, large_callable>(1); + + static_assert(!is_constructible_v, move_only_function>); + static_assert(!is_constructible_v, move_only_function>); +#endif // defined(__cpp_noexcept_function_type) + constexpr bool is_64_bit = sizeof(void*) > 4; // Moves from function to move_only_function @@ -153,6 +179,16 @@ int main() { test_wrapped_call, function, small_callable>(0); alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + // Moves from function to abominable move_only_function + alloc_checker{1}, test_wrapped_call, function, small_callable>(1); + alloc_checker{2}, test_wrapped_call, function, large_callable>(1); + alloc_checker{1}, test_wrapped_call, function, small_callable>(1); + alloc_checker{2}, test_wrapped_call, function, large_callable>(1); + +#ifdef __cpp_noexcept_function_type + static_assert(!is_constructible_v, function>); +#endif // defined(__cpp_noexcept_function_type) + // nulls alloc_checker{0}, test_plain_null>(true); alloc_checker{0}, test_plain_null>(false); From 062fca720b1a336317df23f0b41bf834feb92cbc Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 17:01:49 +0200 Subject: [PATCH 03/12] inner function --- stl/inc/functional | 3 ++- .../test.cpp | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 65ad553b2d..379efc247a 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1576,6 +1576,7 @@ template class _Function_base { public: using result_type = _Rx; + using _Signature_no_cv_noex = _Rx(_Types...); struct _Impl_t { // A per-callable-type structure acting as a virtual function table. // Using vtable emulations gives more flexibility for optimizations and reduces the amount of RTTI data. @@ -2051,7 +2052,7 @@ 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)"); - if constexpr (is_same_v<_Vt, function<_Signature...>>) { + if constexpr (is_same_v<_Vt, function>) { this->template _Construct_with_old_fn<_Vt>(_STD forward<_Fn>(_Callable)); } else { if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> 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 a2df15bcb1..7c7f6e5cbd 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 @@ -69,12 +69,12 @@ struct copy_counter { int count = 0; }; -using fn_type = int(copy_counter); +using fn_type = int(copy_counter); using fn_type_r = int(copy_counter) &; -using fn_type_c = int(copy_counter) const; +using fn_type_c = int(copy_counter) const; #ifdef __cpp_noexcept_function_type -using fn_type_nx = int(copy_counter) noexcept; +using fn_type_nx = int(copy_counter) noexcept; #endif // defined(__cpp_noexcept_function_type) @@ -180,10 +180,12 @@ int main() { alloc_checker{1}, test_wrapped_call, function, large_callable>(0); // Moves from function to abominable move_only_function - alloc_checker{1}, test_wrapped_call, function, small_callable>(1); - alloc_checker{2}, test_wrapped_call, function, large_callable>(1); - alloc_checker{1}, test_wrapped_call, function, small_callable>(1); - alloc_checker{2}, test_wrapped_call, function, large_callable>(1); + alloc_checker{is_64_bit ? 0 : 1}, + test_wrapped_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + alloc_checker{is_64_bit ? 0 : 1}, + test_wrapped_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_call, function, large_callable>(0); #ifdef __cpp_noexcept_function_type static_assert(!is_constructible_v, function>); From 2569e12faac32efc7ac291e07206571c08cd91bb Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 17:24:07 +0200 Subject: [PATCH 04/12] inner move_only_function --- stl/inc/functional | 11 +++++++++++ .../GH_005504_avoid_function_call_wrapping/test.cpp | 12 ++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 379efc247a..beebbfc684 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -2063,11 +2063,22 @@ public: } } + if constexpr (_Is_specialization_v<_Vt, move_only_function>) { + if constexpr (is_same_v) { + this->_Checked_move(this->_Data, _Callable._Data); + _Callable._Reset_to_null(); + return; + } + } + using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; this->template _Construct_with_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable)); } } + template + friend class move_only_function; + template requires _Enable_in_place_constructor<_Fn, _CTypes...> explicit move_only_function(in_place_type_t<_Fn>, _CTypes&&... _Args) { 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 7c7f6e5cbd..ce96590e20 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 @@ -154,10 +154,10 @@ int main() { alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); // Abominables and noexcept specifier - alloc_checker{1}, test_wrapped_call, move_only_function, small_callable>(1); - alloc_checker{2}, test_wrapped_call, move_only_function, large_callable>(1); - alloc_checker{1}, test_wrapped_call, move_only_function, small_callable>(1); - alloc_checker{2}, test_wrapped_call, move_only_function, large_callable>(1); + 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, move_only_function, small_callable>(0); + alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); static_assert(!is_constructible_v, move_only_function>); static_assert(!is_constructible_v, move_only_function>); @@ -165,8 +165,8 @@ int main() { static_assert(!is_constructible_v, move_only_function>); #ifdef __cpp_noexcept_function_type - alloc_checker{1}, test_wrapped_call, move_only_function, small_callable>(1); - alloc_checker{2}, test_wrapped_call, move_only_function, large_callable>(1); + alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); + alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); static_assert(!is_constructible_v, move_only_function>); static_assert(!is_constructible_v, move_only_function>); From d6e3032acb66ac31c3edcf833da5c549267dae47 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 17:46:25 +0200 Subject: [PATCH 05/12] format --- stl/inc/functional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/functional b/stl/inc/functional index beebbfc684..76421ba6b1 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1575,7 +1575,7 @@ _NODISCARD void* _Function_new_large(_CTypes&&... _Args) { template class _Function_base { public: - using result_type = _Rx; + using result_type = _Rx; using _Signature_no_cv_noex = _Rx(_Types...); struct _Impl_t { // A per-callable-type structure acting as a virtual function table. From e3d13adf333fc90fb5e7a1d7b3457b8870c1c862 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 17:48:14 +0200 Subject: [PATCH 06/12] -unnecessary repetitions --- .../std/tests/GH_005504_avoid_function_call_wrapping/test.cpp | 3 --- 1 file changed, 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 ce96590e20..2a9f4954fb 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 @@ -160,8 +160,6 @@ int main() { alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); static_assert(!is_constructible_v, move_only_function>); - static_assert(!is_constructible_v, move_only_function>); - static_assert(!is_constructible_v, move_only_function>); static_assert(!is_constructible_v, move_only_function>); #ifdef __cpp_noexcept_function_type @@ -169,7 +167,6 @@ int main() { alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); static_assert(!is_constructible_v, move_only_function>); - static_assert(!is_constructible_v, move_only_function>); #endif // defined(__cpp_noexcept_function_type) constexpr bool is_64_bit = sizeof(void*) > 4; From febad5589d9935bafefcde56c5cc353fb915cb51 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 17:51:20 +0200 Subject: [PATCH 07/12] -newline --- tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp | 1 - 1 file changed, 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 2a9f4954fb..00cec5c30b 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 @@ -77,7 +77,6 @@ using fn_type_c = int(copy_counter) const; using fn_type_nx = int(copy_counter) noexcept; #endif // defined(__cpp_noexcept_function_type) - struct small_callable { const int context = 42; From ffd98f88bc0082ea218ddee4a96c675588071b23 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 18:37:49 +0200 Subject: [PATCH 08/12] clearer name --- stl/inc/functional | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 76421ba6b1..e0a0f8d84a 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -1575,8 +1575,8 @@ _NODISCARD void* _Function_new_large(_CTypes&&... _Args) { template class _Function_base { public: - using result_type = _Rx; - using _Signature_no_cv_noex = _Rx(_Types...); + using result_type = _Rx; + using _Signature_without_cv_ref_noex = _Rx(_Types...); struct _Impl_t { // A per-callable-type structure acting as a virtual function table. // Using vtable emulations gives more flexibility for optimizations and reduces the amount of RTTI data. @@ -2052,7 +2052,7 @@ 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)"); - if constexpr (is_same_v<_Vt, function>) { + if constexpr (is_same_v<_Vt, function>) { this->template _Construct_with_old_fn<_Vt>(_STD forward<_Fn>(_Callable)); } else { if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> @@ -2064,7 +2064,8 @@ public: } if constexpr (_Is_specialization_v<_Vt, move_only_function>) { - if constexpr (is_same_v) { + if constexpr (is_same_v) { this->_Checked_move(this->_Data, _Callable._Data); _Callable._Reset_to_null(); return; From ec48ebb43884f1b18ade34c13a34a673e9eb5154 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 18:47:53 +0200 Subject: [PATCH 09/12] avoid emitting dead code --- stl/inc/functional | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index e0a0f8d84a..2d8b73c5ef 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -2033,6 +2033,19 @@ private: is_constructible_v, initializer_list<_Ux>&, _CTypes...> && _Call::template _Is_callable_from>; + template + friend class move_only_function; + + template + static constexpr bool _Is_move_only_fuction_varying_cv_ref_noex() { + if constexpr (_Is_specialization_v<_Vt, move_only_function>) { + return is_same_v; + } else { + return false; + } + } + public: using typename _Call::result_type; @@ -2063,23 +2076,16 @@ public: } } - if constexpr (_Is_specialization_v<_Vt, move_only_function>) { - if constexpr (is_same_v) { - this->_Checked_move(this->_Data, _Callable._Data); - _Callable._Reset_to_null(); - return; - } + if constexpr (_Is_move_only_fuction_varying_cv_ref_noex<_Vt>()) { + this->_Checked_move(this->_Data, _Callable._Data); + _Callable._Reset_to_null(); + } else { + using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; + 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 - friend class move_only_function; - template requires _Enable_in_place_constructor<_Fn, _CTypes...> explicit move_only_function(in_place_type_t<_Fn>, _CTypes&&... _Args) { From d726b1265fa3ff4f2a495ec04aa635562b15a85e Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 19:36:10 +0200 Subject: [PATCH 10/12] let's not pretend it's non-static --- stl/inc/functional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/functional b/stl/inc/functional index 2d8b73c5ef..ce38d49ea9 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -2077,7 +2077,7 @@ public: } if constexpr (_Is_move_only_fuction_varying_cv_ref_noex<_Vt>()) { - this->_Checked_move(this->_Data, _Callable._Data); + _Call::_Checked_move(this->_Data, _Callable._Data); _Callable._Reset_to_null(); } else { using _VtInvQuals = _Call::template _VtInvQuals<_Vt>; From 2c1002c505da5d4f93d9640f25ef0161a6612bb9 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 30 Nov 2025 16:58:09 +0200 Subject: [PATCH 11/12] no lvalue reference --- stl/inc/functional | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index ce38d49ea9..1c99860260 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -2065,7 +2065,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)"); - if constexpr (is_same_v<_Vt, function>) { + if constexpr (!is_lvalue_reference_v<_Fn> + && is_same_v<_Vt, function>) { this->template _Construct_with_old_fn<_Vt>(_STD forward<_Fn>(_Callable)); } else { if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> @@ -2076,7 +2077,7 @@ public: } } - if constexpr (_Is_move_only_fuction_varying_cv_ref_noex<_Vt>()) { + if constexpr (!is_lvalue_reference_v<_Fn> && _Is_move_only_fuction_varying_cv_ref_noex<_Vt>()) { _Call::_Checked_move(this->_Data, _Callable._Data); _Callable._Reset_to_null(); } else { From ed5dfc0b26096f97a2d05211433d4167d77e741a Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 30 Nov 2025 19:27:12 +0200 Subject: [PATCH 12/12] this part is not needed, move_only_function can't do this anyway --- stl/inc/functional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/functional b/stl/inc/functional index 1c99860260..073a2d4596 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -2077,7 +2077,7 @@ public: } } - if constexpr (!is_lvalue_reference_v<_Fn> && _Is_move_only_fuction_varying_cv_ref_noex<_Vt>()) { + if constexpr (_Is_move_only_fuction_varying_cv_ref_noex<_Vt>()) { _Call::_Checked_move(this->_Data, _Callable._Data); _Callable._Reset_to_null(); } else {