the-moisrex / webpp

C++ web framework | web development can be done with C++ as well.
https://t.me/webpp
MIT License
133 stars 9 forks source link

std::function with custom allocator #115

Open the-moisrex opened 2 years ago

the-moisrex commented 2 years ago

I think we do need a std::function alternative with allocator/traits support. We had such thing, but it's been deprecated.

Example usage in: mustache_view/basic_renderer

Example usage in: query_builder

Todo:

the-moisrex commented 2 years ago

A couple of links to consider:

  1. CxxFunctionBenchmark
  2. Avoiding The Performance Hazzards of std::function
the-moisrex commented 2 years ago

I'm intentionally excluding Small Objection Optimization from it in order to let the user of the istl::function choose the amount of SOO with the help of stack_allocator (#153).

the-moisrex commented 2 years ago

Oh man, I'm really stuck at copying/moving istl::function<..., AllocA> to istl::function<..., AllocB>.

I have tried many possible solutions so far, I've been working on this for days man!

the-moisrex commented 2 years ago

Okay, in order to fix this, I'm gonna write the journey as I go here, let's see if I can fix this or not.

Possible Solutions / Mistakes

1. Convert run_action to an object

Storing a random type, is only possible through function pointers. When the user calls operator= for a lambda, the caller and action_runner methods (which they technically can be combined into one, but it's not recommended because the CPU has to do more work in order to call the function) will be changed to pointing to the new function pointers that have been created with the template, but a function pointer doesn't care about whether or not the function was a templated function or not so this way we're storing the Callable and FunctionType (which is istl::function<..., ...>) types. The same thing happens in vtables.

2. Using rebind

I've already tried several ways of rebind method. I can't get them to work because the copy_from/move_from functions don't know the type of the run_action.

3. Taking out the necessity of FunctionType from run_action

See, the thing is, we need FunctionType or at least allocator_type that is inside the function type, in order to deallocate, and destroy the allocated Callable which can have any size it wants. We do the construct and allocate outside of the run_action, but the copy/move construct, deallocate, and destroy need to be called inside the run_action because they need to have access to the Callable type.

Of course, deallocate doesn't exactly needs Callable type because we already have a get_size action which means we can deallocate that amount of size.

My idea here is this: alloc_traits::destroy calls the Callable's destructor. We can do that without the help of the allocator ourselves. The standard allocators do this anyway. But the problem is that when the user provides a custom allocator, this means that we're skipping the user provided allocator which if we wanted to do that we would be better off with std::function witch essentially doesn't have allocators nowadays.

4. Passing function pointers to run_action

The good thing about run_action is that it takes a void* as input. So we can pass a function pointer to it so it can call it and somehow pass the callable type back to the move_to/clone_to functions.

5. Disallow copying/moving from a different allocator type

This way, we would be copying/moving the whole istl::function as a Callable which add another level of indirection for when you want to actually call the object. This has a performance hit so I'm not comfortable with this one.