Skip to content
This repository has been archived by the owner on Jan 15, 2021. It is now read-only.

Add a new Function object #61

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Conversation

bremoran
Copy link
Contributor

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
Copy link
Contributor Author

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

@0xc0170
Copy link
Contributor

0xc0170 commented Jan 22, 2016

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: - std::is_empty - std::remove_reference - std::move - std::forward. We should sync about this

@bremoran
Copy link
Contributor Author

@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.

f(f), storage(t)
{}

virtual ReturnType operator () (ArgTypes&&... Args) {
Copy link
Member

Choose a reason for hiding this comment

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

why this is virtual ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It probably doesn't need to be.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I didn't saw that CaptureFirst inherit from FunctionInterface, it makes sense if it is virtual.

@bremoran bremoran force-pushed the functional branch 2 times, most recently from 4a1560c to 5872886 Compare January 22, 2016 10:39
@bremoran
Copy link
Contributor Author

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
Copy link
Contributor Author

cc @Patater

@bogdanm
Copy link
Contributor

bogdanm commented Jan 25, 2016

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
Copy link
Contributor Author

@bogdanm ARMmbed/sockets#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
Copy link
Contributor Author

@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
Copy link
Contributor Author

bremoran commented Feb 3, 2016

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

@0xc0170
Copy link
Contributor

0xc0170 commented Feb 4, 2016

+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
Copy link
Contributor Author

bremoran commented Feb 4, 2016

@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.

Functional is built using reference-counted, pool-allocated functors. Function is the reference type for functors.
This commit adds several updates to the Function implementation

* bind_first and bind_last both work
* APIs changed to snake_case
* atomic_incr and atomic_decr are now used instead of Cortex-M3+ primitives
* The first bits of documentation have been added
* Explicit call to ```ExtendablePoolAllocator()```
* Remove unnecessary virtuals
* Add asserts for memory allocation failure
Supports ARMmbed/sockets#58

* Add an API to obtain the internal reference as a ```void *```
* Add an API to call a Function from a ```void *``` reference
* Add an API to call a Function from a ```void *``` reference, then decrement the reference count
* create a polyfill namespace
* move forward to polyfill
* Add a sorted tuple
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants