Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR: a constructor for delegate with a freestanding function #966

Open
positron96 opened this issue Oct 2, 2024 · 11 comments
Open

FR: a constructor for delegate with a freestanding function #966

positron96 opened this issue Oct 2, 2024 · 11 comments
Assignees

Comments

@positron96
Copy link

At the moment, you construct a delegate with a freestanding function like this: my_delegate_type::create<my_function>(), and use it like this: add_callback(my_delegate_type::create<my_function>()).

It would be nice to have an implicit constructor for this case, then this ::create distraction could be omitted and simplified to just add_callback(my_function).

As I understand, this syntax is possible for functors and lambdas, but not for freestanding functions. There are probably obstructions to doing this, otherwise you'd probably have implemented it long ago, but my c++-fu is not strong enough to understand it.

@jwellbelove
Copy link
Contributor

I'll take a look to see if it is possible.

@jwellbelove
Copy link
Contributor

I've been doing C++ for over 20 years, and it still manages to surprise me sometimes, especially convoluted template meta-programming!

@jwellbelove
Copy link
Contributor

jwellbelove commented Oct 4, 2024

I've had a go at this, and the fundamental problem is that you cannot explicitly declare the template parameters for a constructor. They must be deduced from the constructor's argument.
This works fine for a lambda or functor as there is an instance argument to pass in. This is not possible for a freestanding function.

The only way to simplify construction is to make a lambda or functor wrapper around the free function.

auto my_function_lambda = []() { my_function(); };

add_callback(my_delegate_type(my_function_lambda));

@jwellbelove
Copy link
Contributor

jwellbelove commented Oct 4, 2024

With optimisation, the resulting code is still very efficient, even with the lambda and delegate.

For this code and -O1 optimisation, the lambda and delegate reduce to a direct call of the free function.

#include <iostream>
#include "etl/delegate.h"

void free_void()
{
    std::cout << "free_void/n";
}

int main() 
{
      constexpr auto lambda = []() { free_void(); };
      auto d = etl::delegate<void(void)>(lambda);
      d();
}
.LC0:
        .string "free_void/n"
free_void():
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        add     rsp, 8
        ret
main:
        sub     rsp, 8
        call    free_void()
        mov     eax, 0
        add     rsp, 8
        ret

https://godbolt.org/z/PTxMcexsW

@positron96
Copy link
Author

Hi, thanks for looking into this!
While you clearly show that adding lambda has no impact on performance, I would argue that it makes source code nosier still, meaning it hides the simple intent of adding a callback by introducing implementation-related entities.

the fundamental problem is that you cannot explicitly declare the template parameters for a constructor. They must be deduced from the constructor's argument.

Do you know if it's possible to explicitly cast a function (pointer) to a type to make compiler deduce template parameters?
E.g.:

my_delegate_t callback{my_delegate_t::fn_t(my_function)};

where fn_t is defined as something like

template <typename TReturn, typename... TParams>
class delegate<TReturn(TParams...)> {
   using fn_t = TReturn(TParams...);
}

I tried to play with library code, but every time the compiler complained.

However in terms of verbosity this already approaches existing solution with ::create<>

@jwellbelove
Copy link
Contributor

I had a play this morning with creating a make_delgate template function, which seemed to work, although it only works for C++14 and above.

add_callback(etl::make_delegate<my_function>());

@jwellbelove
Copy link
Contributor

jwellbelove commented Oct 9, 2024

I have worked out three etl::make_delegate functions to simplify delegate construction for free and member functions.
Note: These are >= C++14 only.

void free_int(int);

struct S
{
  void member(int);
  void member_const(int) const;
}

static S s;

auto d1 = etl::make_delegate<free_int>();

auto d2 = etl::make_delegate<S, &S::member, s>();
auto d3 = etl::make_delegate<S, &S::member_const, s>();

auto d4 = etl::make_delegate<S, &S::member>(s);
auto d5 = etl::make_delegate<S, &S::member_const>(s);

@positron96
Copy link
Author

So, compared to existing solution, this saves an extra template specialization, right?

auto d = etl::delegate<int(int)>::create<free_func>();
auto d = etl::delegate<int(int)>::create<Test, test, &Test::member_function>();

@jwellbelove
Copy link
Contributor

Yes, that's correct. make_delegate deduces the function signature from the function pointer, so that it doesn't have to be explicitly declared.

@positron96
Copy link
Author

Ok. Doesn't completely solve it, but this will make using delegates easier!

@jwellbelove
Copy link
Contributor

Actually, I've discovered that the syntax is only valid for C++17 and up.

jwellbelove added a commit that referenced this issue Nov 10, 2024
…opying/assigning from delegate with mismatching signature

#966 A constructor for delegate with a freestanding function
@jwellbelove jwellbelove self-assigned this Nov 30, 2024
jwellbelove added a commit that referenced this issue Dec 19, 2024
…opying/assigning from delegate with mismatching signature

#966 A constructor for delegate with a freestanding function
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

2 participants