Skip to content

Commit d9faeab

Browse files
committed
remove copying when invoking objects stored in sbo
Resolves: #23 Right now the invoke holders take the storage by-value which is strictly incorrect when using SBO as it creates a copy of the callable which leads to surprising results in user-code that expect multiple calls to a lambda with a mutable capture. Signed-off-by: Christian Mazakas <christian.mazakas@gmail.com>
1 parent 0776d62 commit d9faeab

2 files changed

Lines changed: 41 additions & 7 deletions

File tree

include/boost/compat/move_only_function.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ bool is_nullary_arg( F f )
247247
template<bool NoEx, class R, class ...Args>
248248
struct mo_invoke_function_holder
249249
{
250-
static R invoke_function( storage s, Args&&... args) noexcept( NoEx )
250+
static R invoke_function( storage const& s, Args&&... args) noexcept( NoEx )
251251
{
252252
auto f = reinterpret_cast<R(*)( Args... )>( s.pfn_ );
253253
return compat::invoke_r<R>( f, std::forward<Args>( args )... );
@@ -257,7 +257,7 @@ struct mo_invoke_function_holder
257257
template<ref_quals RQ, bool Const, bool NoEx, class F, class R, class ...Args>
258258
struct mo_invoke_object_holder
259259
{
260-
static R invoke_object( storage s, Args&&... args ) noexcept( NoEx )
260+
static R invoke_object( storage const& s, Args&&... args ) noexcept( NoEx )
261261
{
262262
using T = remove_reference_t<F>;
263263
using cv_T = conditional_t<Const, add_const_t<T>, T>;
@@ -274,7 +274,7 @@ struct mo_invoke_object_holder
274274
template<ref_quals RQ, bool Const, bool NoEx, class F, class R, class ...Args>
275275
struct mo_invoke_local_holder
276276
{
277-
static R invoke_local( storage s, Args&&... args ) noexcept( NoEx )
277+
static R invoke_local( storage const& s, Args&&... args ) noexcept( NoEx )
278278
{
279279
using T = remove_reference_t<F>;
280280
using cv_T = conditional_t<Const, add_const_t<T>, T>;
@@ -285,7 +285,7 @@ struct mo_invoke_local_holder
285285
>
286286
>;
287287

288-
return compat::invoke_r<R>( static_cast<cv_ref_T>( *static_cast<cv_T*>( s.addr() ) ), std::forward<Args>( args )... );
288+
return compat::invoke_r<R>( static_cast<cv_ref_T>( *static_cast<cv_T*>( const_cast<storage&>( s ).addr() ) ), std::forward<Args>( args )... );
289289
}
290290
};
291291

@@ -485,9 +485,9 @@ struct move_only_function_base
485485

486486
detail::storage s_;
487487
#if defined(__cpp_noexcept_function_type)
488-
R ( *invoke_ )( detail::storage, Args&&... ) noexcept( NoEx ) = nullptr;
488+
R ( *invoke_ )( detail::storage const&, Args&&... ) noexcept( NoEx ) = nullptr;
489489
#else
490-
R ( *invoke_ )( detail::storage, Args&&... ) = nullptr;
490+
R ( *invoke_ )( detail::storage const&, Args&&... ) = nullptr;
491491
#endif
492492
void ( *manager_ )( op_type, detail::storage&, detail::storage* ) = &manage_empty;
493493
};

test/move_only_function_test.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
#include <boost/core/lightweight_test.hpp>
1414
#include <boost/core/lightweight_test_trait.hpp>
1515

16-
#include <functional>
16+
#include <array>
17+
#include <cstdint>
1718
#include <memory>
1819
#include <type_traits>
1920

@@ -25,6 +26,11 @@
2526
# pragma clang diagnostic ignored "-Wself-move"
2627
#endif
2728

29+
#ifdef _MSC_VER
30+
#pragma warning(disable: 4789) // false buffer overrun warning in test_mutable_lambda()
31+
#endif
32+
33+
2834
using std::is_same;
2935
using std::is_constructible;
3036
using std::is_nothrow_constructible;
@@ -845,11 +851,39 @@ static void test_conv()
845851
}
846852
}
847853

854+
static void test_mutable_lambda()
855+
{
856+
{
857+
// Within SBO limits.
858+
int captured = 0;
859+
move_only_function<int()> func = [captured]() mutable { return ++captured; };
860+
861+
BOOST_TEST_EQ( func(), 1 );
862+
BOOST_TEST_EQ( func(), 2 );
863+
864+
move_only_function<int()> func2(std::move(func));
865+
BOOST_TEST_EQ( func2(), 3 );
866+
}
867+
868+
{
869+
// Too large for SBO.
870+
std::array<std::uint8_t, 256> captured = {{}};
871+
move_only_function<int()> func = [captured]() mutable { return ++captured[0]; };
872+
873+
BOOST_TEST_EQ( func(), 1 );
874+
BOOST_TEST_EQ( func(), 2 );
875+
876+
move_only_function<int()> func2(std::move(func));
877+
BOOST_TEST_EQ( func2(), 3 );
878+
}
879+
}
880+
848881
int main()
849882
{
850883
test_call();
851884
test_traits();
852885
test_conv();
886+
test_mutable_lambda();
853887

854888
return boost::report_errors();
855889
}

0 commit comments

Comments
 (0)