Skip to content

mw/com: Fix method signature to avoid copies of return type#369

Open
bemerybmw wants to merge 10 commits into
mainfrom
brem_methods_dont_copy_return
Open

mw/com: Fix method signature to avoid copies of return type#369
bemerybmw wants to merge 10 commits into
mainfrom
brem_methods_dont_copy_return

Conversation

@bemerybmw

@bemerybmw bemerybmw commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Depends-on: #514

@bemerybmw bemerybmw added the invalid This doesn't seem right label Apr 28, 2026
@bemerybmw bemerybmw force-pushed the brem_methods_dont_copy_return branch 2 times, most recently from 44e9aa3 to b336e39 Compare April 30, 2026 16:10
@bemerybmw bemerybmw changed the title Brem methods dont copy return mw/com: Fix method signature to avoid copies of return type Apr 30, 2026
@bemerybmw bemerybmw removed the invalid This doesn't seem right label Apr 30, 2026
@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. If this PR is still relevant, please leave a comment or push new changes to keep it open.

@github-actions github-actions Bot added the stale label May 21, 2026
@bemerybmw bemerybmw force-pushed the brem_methods_dont_copy_return branch from b336e39 to cec7195 Compare May 27, 2026 15:38
Comment thread score/mw/com/impl/methods/method_handler_checker.h Outdated
@github-actions github-actions Bot removed the stale label May 28, 2026
@bemerybmw bemerybmw force-pushed the brem_methods_dont_copy_return branch from cec7195 to 5729f2f Compare May 28, 2026 09:38
@bemerybmw bemerybmw marked this pull request as ready for review May 28, 2026 11:58
constexpr bool is_return_type_not_void = !std::is_same_v<ReturnType, void>;
if constexpr (is_return_type_not_void)
{
const auto typed_return_ptr_tuple = Deserialize<ReturnType>(type_erased_return.value());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could use DeserializeArg<ReturnType>() directly ... much simpler?

::testing::Types<MethodTypeAndHandlerType<TestMethodType, TestMethodHandlerType>,
MethodTypeAndHandlerType<void(int), void(const int&)>,
MethodTypeAndHandlerType<void(int, int), void(const int&, const int&)>,
MethodTypeAndHandlerType<void(const double, int), void(const double&, const int&)>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interessting. So you have a const in front of an in-arg .... in the user provided method-signature ... "technically" this is uneeded as with treat in-args as const anyhow! But here you are intentionally checking, that we also can cope with such a signature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, exactly.

@@ -99,17 +83,39 @@ TEST(SkeletonMethodTest, ClassTypeDependsOnMethodType)
"Class type does not depend on event data type");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

event data type

method signature?

}

EmptySkeleton empty_skeleton_{std::make_unique<mock_binding::Skeleton>(), kInstanceIdWithLolaBinding};
std::string method_name_{"dummy_method"};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const


template <typename T = SampleDataType, typename = std::enable_if_t<EnableSet && std::is_same_v<T, SampleDataType>>>
score::Result<MethodReturnTypePtr<T>> Set(SampleDataType& new_field_value) noexcept
score::Result<MethodReturnTypePtr<T>> Set(const SampleDataType& new_field_value) noexcept

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Now I got aware, that we don't expose the zero-copy/allocate variant of method calls to the user ... I guess this would be just a small addition ... but let's wait til someone really asks for it :)

return a + b;
};
auto handler_with_in_args_and_return =
[](std::int32_t& return_value, const std::int32_t& a, const std::int32_t& b) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a -> void ... makes it "clearer"?

@bemerybmw bemerybmw force-pushed the brem_methods_dont_copy_return branch 3 times, most recently from 4623c8f to 265b367 Compare June 1, 2026 12:48
write_head += in_type_1_size;
new (write_head) InType2(in_arg_2);
write_head += in_type_2_size;
new (write_head) InType2(in_arg_3);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the in_args_buffer_ is still sized with 2 elements here.
static constexpr std::size_t in_args_buffer_size = sizeof(InType1) + sizeof(InType2);
shouldn't this be
static constexpr std::size_t in_args_buffer_size = sizeof(InType1) + sizeof(InType2) + sizeof(InType2); ?

"ReturnType is non void. Thus, type_erased_result needs to have a value!");
ReturnType res = std::apply(callable_invoker, std::forward<InArgPtrTuple>(typed_in_arg_ptrs));
SerializeArgs<ReturnType>(type_erased_return.value(), res);
auto* const typed_return_ptr = DeserializeArg<ReturnType>(type_erased_return.value());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah! we populate inplace, cool,
but, does it not weaken the "safe for trivially copyable" check?
in SerializeArgs route before we had this explicit check static_assert(std::is_trivially_copyable_v before we populate type_erased_return.
i think this is fine today though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I actually don't think we have that requirement for deserializing. Deserializing is basically just casting the type, there's no copying done. In practice, we always serialize with the function in this file which already confirms that the type is trivially copyable but in theory, you could create a type erased buffer containing a non trivially copyable type (but the caller has to take that it's correctly created in the buffer) and then deserialize it without making a copy which should not be a problem. If the user then wants to copy it out of the buffer, it's now strongly typed and so the copy constructor can be called.

{
detail::MemoryBufferAccessor memory_buffer_accessor{src_buffer};
auto tuple_of_args = detail::Deserialize<Arg>(memory_buffer_accessor);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(std::tuple_size_v<decltype(tuple_of_args)> == 1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be a static assert?

MethodTypeAndHandlerType<void(int), void(const int&)>,
MethodTypeAndHandlerType<void(int, int), void(const int&, const int&)>,
MethodTypeAndHandlerType<void(const double, int), void(const double&, const int&)>,
MethodTypeAndHandlerType<void(int, int), void(const int&, const int&)>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread score/mw/com/impl/skeleton_field.h Outdated
Result<void> RegisterSetHandler(CallableType&& set_handler)
{
static_assert(std::is_invocable_v<CallableType, FieldType&>,
static_assert(std::is_invocable_r_v<void, CallableType, FieldType&>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this really do something?
somehow, my static asserts here always work even when the ret type is non void.

score::cpp::callback<FixtureMethodType> test_callback{};

std::ignore = method.RegisterHandler(std::move(test_callback));
// // When a Register call is issued at the binding independent level

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// // When a Register call is issued at the binding independent level
// When a Register call is issued at the binding independent level

bemerybmw added 3 commits June 9, 2026 15:28
This is the copy variant of set which takes a field value and returns
the new value via the MethodReturnTypePtr. The provided value will not
be modified so it's misleading that it's non-const.
@bemerybmw bemerybmw force-pushed the brem_methods_dont_copy_return branch 3 times, most recently from 804eec6 to beeff8d Compare June 9, 2026 13:59
@bemerybmw bemerybmw force-pushed the brem_methods_dont_copy_return branch from beeff8d to cbc493d Compare June 9, 2026 14:34

ASSERT_TRUE(unit.my_setter_field_.Update(TestSampleType{1U}).has_value());
ASSERT_TRUE(unit.my_setter_field_.PrepareOffer().has_value());
TEST_F(SkeletonFieldSetHandlerTest, CallingPrepareOfferWithoutRegisteringSetHandlerReturnsError)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the same test as here

TEST_F(SkeletonFieldSetHandlerTest, PrepareOfferFailsWhenSetHandlerNotRegistered)

return set_method_->RegisterHandler(std::move(wrapped_callback));

auto state = std::make_unique<std::pair<SkeletonField*, CallableType>>(
this, std::forward<CallableType>(user_set_handler));

@KrishaDeshkool KrishaDeshkool Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wont this cause the same "use after free" issue?
since we store "this" during the registration into the stored set_handler and i dont think we update this when we move the SkeletonField then won't this still be pointing to the moved from object?
or am i missing something?

"Registered method callable must have a single non-const reference argument of type FieldType&!");

// Since set_handler can be an lvalue reference or an rvalue reference, we ideally would store it as a
// universal reference in the type_erased_handler. However, in C++17, this is not supported. Instead, we create

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to me this sounds like this is C++17 only issue (storing a universal ref),
It is only used by compiler to figure out what is the type of the args, once it is figures that out it would be either an L or an R value, so can we just drop that confusing line ?
https://stackoverflow.com/questions/14757474/how-to-store-universal-references

// Since set_handler can be an lvalue reference or an rvalue reference, we ideally would store it as a
// universal reference in the type_erased_handler. However, in C++17, this is not supported. Instead, we create
// an callable here which will be called below by another lambda which explicitly stores the callback as either
// an lvalue reference or an rvalue reference depending on how it was passed in.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants