From fbad5e0834cf12b73746716afc58f1c0ec31e1d7 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 20 Jan 2026 07:30:25 -0800 Subject: [PATCH 1/3] add test for in-place mutation of SBO-stored callables --- test/move_only_function_test.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/move_only_function_test.cpp b/test/move_only_function_test.cpp index 00917ca..ad9566b 100644 --- a/test/move_only_function_test.cpp +++ b/test/move_only_function_test.cpp @@ -13,7 +13,8 @@ #include #include -#include +#include +#include #include #include @@ -845,11 +846,39 @@ static void test_conv() } } +static void test_mutable_lambda() +{ + { + // Within SBO limits. + int captured = 0; + move_only_function func = [captured]() mutable { return ++captured; }; + + BOOST_TEST_EQ( func(), 1 ); + BOOST_TEST_EQ( func(), 2 ); + + move_only_function func2(std::move(func)); + BOOST_TEST_EQ( func2(), 3 ); + } + + { + // Too large for SBO. + std::array captured = {{}}; + move_only_function func = [captured]() mutable { return ++captured[0]; }; + + BOOST_TEST_EQ( func(), 1 ); + BOOST_TEST_EQ( func(), 2 ); + + move_only_function func2(std::move(func)); + BOOST_TEST_EQ( func2(), 3 ); + } +} + int main() { test_call(); test_traits(); test_conv(); + test_mutable_lambda(); return boost::report_errors(); } From 945b2862b4db538e14f48e1fbae4ad4a03220011 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 20 Jan 2026 07:30:53 -0800 Subject: [PATCH 2/3] invoke internal storage via reference This prevents the code from erroneously copying the storage which causes surprising behavior as noted here: https://github.com/boostorg/compat/issues/23 --- include/boost/compat/move_only_function.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/compat/move_only_function.hpp b/include/boost/compat/move_only_function.hpp index 7669444..5511b52 100644 --- a/include/boost/compat/move_only_function.hpp +++ b/include/boost/compat/move_only_function.hpp @@ -247,7 +247,7 @@ bool is_nullary_arg( F f ) template struct mo_invoke_function_holder { - static R invoke_function( storage s, Args&&... args) noexcept( NoEx ) + static R invoke_function( storage const& s, Args&&... args) noexcept( NoEx ) { auto f = reinterpret_cast( s.pfn_ ); return compat::invoke_r( f, std::forward( args )... ); @@ -257,7 +257,7 @@ struct mo_invoke_function_holder template struct mo_invoke_object_holder { - static R invoke_object( storage s, Args&&... args ) noexcept( NoEx ) + static R invoke_object( storage const& s, Args&&... args ) noexcept( NoEx ) { using T = remove_reference_t; using cv_T = conditional_t, T>; @@ -274,7 +274,7 @@ struct mo_invoke_object_holder template struct mo_invoke_local_holder { - static R invoke_local( storage s, Args&&... args ) noexcept( NoEx ) + static R invoke_local( storage const& s, Args&&... args ) noexcept( NoEx ) { using T = remove_reference_t; using cv_T = conditional_t, T>; @@ -285,7 +285,7 @@ struct mo_invoke_local_holder > >; - return compat::invoke_r( static_cast( *static_cast( s.addr() ) ), std::forward( args )... ); + return compat::invoke_r( static_cast( *static_cast( const_cast( s ).addr() ) ), std::forward( args )... ); } }; @@ -485,9 +485,9 @@ struct move_only_function_base detail::storage s_; #if defined(__cpp_noexcept_function_type) - R ( *invoke_ )( detail::storage, Args&&... ) noexcept( NoEx ) = nullptr; + R ( *invoke_ )( detail::storage const&, Args&&... ) noexcept( NoEx ) = nullptr; #else - R ( *invoke_ )( detail::storage, Args&&... ) = nullptr; + R ( *invoke_ )( detail::storage const&, Args&&... ) = nullptr; #endif void ( *manager_ )( op_type, detail::storage&, detail::storage* ) = &manage_empty; }; From 01d7279a167ab0cc07cbc4f1b9e32a9bc6aa91be Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 20 Jan 2026 07:32:16 -0800 Subject: [PATCH 3/3] suppress erroneous buffer-overrun warning in msvc The msvc optimizer runs into a bug in the mutable lambda tests for move_only_function where it believes we're placing a large object into the small buffer, resulting in compiler errors: D:\a\compat\boost-root\boost\compat\move_only_function.hpp(400) : error C2220: the following warning is treated as an error D:\a\compat\boost-root\boost\compat\move_only_function.hpp(400) : warning C4789: in function 'void __cdecl test_mutable_lambda(void)' buffer 'func2' of size 32 bytes will be overrun; 256 bytes will be written starting at offset 0 The code, however, is correct and eschews the SBO path for sufficiently large Callables, as if one manually adds a `throw 1234;` statement to the SBO paths, they are not hit when the test case in question is run. We choose to manually suppress the warning as no other compiler emits it nor is it found by any of our sanitizer CI jobs, including locally. --- test/move_only_function_test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/move_only_function_test.cpp b/test/move_only_function_test.cpp index ad9566b..4b81485 100644 --- a/test/move_only_function_test.cpp +++ b/test/move_only_function_test.cpp @@ -26,6 +26,10 @@ # pragma clang diagnostic ignored "-Wself-move" #endif +#ifdef _MSC_VER +#pragma warning(disable: 4789) // false buffer overrun warning in test_mutable_lambda() +#endif + using std::is_same; using std::is_constructible; using std::is_nothrow_constructible;