ARMmbed / core-util

DEPRECATED: Mbed 3 utilities library
Other
12 stars 17 forks source link

Add a new Function object #61

Open bremoran opened 8 years ago

bremoran commented 8 years ago

Function could replace FunctionPointer. This PR is not quite finished yet, since it requires more documentation. There is also some cleanup that could be done on bind_first/bind_last, which currently take completely different approaches to how they create the new Function.

Excerpt from the Function documentation:

In order to maintain compatibility with existing code, Function is intended to be duck-type compatible with the FunctionPointer family of classes, but it is intentionally not named the same way. A family of compatibility classes is provided to support conversion from FunctionPointer code to Function. These compatibility classes will be deprecated in an upcoming release.

Superficially, Function is intented to appear similar to std::function. However, Function has a number of key differences. Function uses reference counting to limit the copying of large callable objects, such as lambdas with large capture lists or Functions with large or many arguments. Function also uses pool allocation so that objects can be created in interrupt context without using malloc(). These choices are to overcome two specific limitations of std::function

  1. Copy-constructing a functor requires copy-constructing its member objects. In the case of lambdas, this means copy-constructing the lambda captures. lambda captures are not guaranteed to have reentrant copy constructors, so lambdas cannot be copy-constructed in interrupt context. Therefore, functors must be created in dynamic memory and only pointers to functors can be used.
  2. Due to 1. creating a functor requires dynamic memory allocation, however malloc is not permitted in interrupt context. As a result functors must be pool allocated.
  3. In order to simplify memory management of functors and due to 1. and 2., functors are reference-counted.
bremoran commented 8 years ago

It appears that compiler-polyfill will need some work to enable this. Specifically, it requires an implementation of std::tuple

0xc0170 commented 8 years ago

Not certain if compiler-polyfil is place for library utilities, more like library-polyfill. @pan- already did already some for their ble testing framework. From his commit message `Provide polyfill for:

bremoran commented 8 years ago

@0xc0170 I think it might be too early to start doing a lib-polyfill repo. Let's start if off in compiler-polyfill, then break it out into its own repo when necessary.

bremoran commented 8 years ago

Added a new set of APIs for C Function escalation.

Suppose there is a C API that looks like this:

typedef void (*foocb) (void*, int, double, char);
int foo(int, char, foocb callback, void * context);

Then, suppose that this should be called in a C++ context, e.g. a lambda. In order to achieve this, the previous commit's static APIs can be used.

Function<void(int,double,char)> foofunc = [](int i, double d, char c) {
    printf("%i, %lf, %c\n", i,d,c);
};
foo(1,'a', &foofunc::call_from_void_dec, foofunc.get_ref());
// If the call is to be used multiple times, then:
foo(1,'a', &foofunc::call_from_void, foofunc.get_ref());

Note that get_ref increments the reference count, so foofunc can even be destroyed. This means that lifetime management is dealt with on behalf of the user.

bremoran commented 8 years ago

cc @Patater

bogdanm commented 8 years ago

I'm not exactly sure what this is supposed to solve, but the idea of doing manual ref counting like this looks quite a bit scary.

bremoran commented 8 years ago

@bogdanm https://github.com/ARMmbed/sockets/issues/58

This deals with a very common problem in the C/C++ boundary layer, without the use of CThunk where it is not required.

bremoran commented 8 years ago

@bogdanm Note that this doesn't have to live in Function. Only the get_ref() member function needs to exist in Function. However, since get_ref() increments the reference count, there probably needs to be a corresponding drop_ref()

bremoran commented 8 years ago

The tuple instantiated above is not optimal. It doesn't perform a full sort, so it does not achieve optimal packing.

0xc0170 commented 8 years ago

+1 for having this syntax available Function<void(int,double,char)> foofunc !

Using similar to std::function, people might try to use it the same way. I did once and failed (use cv qualifier member function with our current fp, see later.) Therefore might be beneficial to share how this differs, or better said what it provides and its known limitations (for a function objects, it's captured nicely in tr1). A quick look at the this changeset a distinction is quite big. For instance, a member function with cv-qualifiers is not covered here.

How are arguments binded? I assume using tuple, first argument plus tuple the rest of arguments? How many arguments can be binded? Is there limitation (FUNCTION_SIZE?)?

Does it allow small object optimization? Looking at the code, seems like not, and we alloc for any member pointer in ctor for Function:

// functional.hpp 112 line
    Function(C *o, ReturnType (C::*fp)(ArgTypes...)) {
        typedef detail::MemberContainer<C, ReturnType(ArgTypes...)> memberFP;
        memberFP * newf = reinterpret_cast<memberFP *>(detail::MemberFPAllocator.alloc());

I am interested to see how call_from_void, and similar work. I'll have a look at this later.

I am missing docs for the hierarchy of classes to build this Function type (where magic lies in), which might answer my questions above.

bremoran commented 8 years ago

@0xc0170 You're right, it needs more documentation and more helper methods.

I think it would be a good idea to make sure that all of the std::function APIs are available on Function.

The tuple is recursively defined and the only constraint is that it must fit in the available allocator.