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

Lambdas #44

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions core-util/v2/FunctionPointer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* mbed Microcontroller Library
* Copyright (c) 2006-2015 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __FUNCTIONPOINTER_HPP__
#define __FUNCTIONPOINTER_HPP__
Copy link
Contributor

Choose a reason for hiding this comment

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

#ifndef __CORE_UTIL_FUNCTIONPOINTER_HPP__

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Even more, though, it should be:
#ifndef __CORE_UTIL_V2_FUNCTIONPOINTER_HPP__


#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <new>
#include <utility>
#include <cstdio>
#include <type_traits>

namespace mbed {
namespace util {


namespace impl{

template<size_t I = 0>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is my understanding correct that I is used here as optional padding? Could we rename it as PADDING?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I is not optional padding. It is extra space for storing functors. Since bound arguments are captured in functors, this is equivalent to:

  • Lambda capture list storage space
  • Generic functor storage space
  • Storage space for bound arguments

I don't think that PADDING captures these use-cases well. Neither does I. I would suggest ExtraSpace, but I prefer my template parameters to be more concise. Suggestions welcome.

Copy link
Contributor

Choose a reason for hiding this comment

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

I is too concise. No matter what you choose, please add a comment to make the intention explicit.

class FunctionPointerStorage {
Copy link
Contributor

Choose a reason for hiding this comment

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

FunctionPointerStorage is only needed within FunctionPointerSize0; perhaps it could be made into a subclass.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FunctionPointerStorage is used in FunctionPointerInterface.

public:
// Forward declaration of an unknown class
class UnknownClass;
// Forward declaration of an unknown member function to this an unknown class
// this kind of declaration is authorized by the standard (see 8.3.3/2 of C++ 03 standard).
// As a result, the compiler will allocate for UnknownFunctionMember_t the biggest size
// and biggest alignment possible for member function.
// This type can be used inside unions, it will help to provide the storage
// with the proper size and alignment guarantees
typedef void (UnknownClass::*UnknownFunctionMember_t)();

union {
void * _static_fp;
Copy link
Contributor

Choose a reason for hiding this comment

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

How is this different from _external_functor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are syntactically equivalent, but semantically different. The static FP is a locally captured pointer, while the _external_functor is a heap-allocated functor, which requires destruction. They are stored separately because adding more equally-sized parameters to a union costs nothing, but the code is more readable.

Copy link
Contributor

Choose a reason for hiding this comment

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

The name _static_fp doesn't state its contract clearly. In general, all of this could read much better with documentation of intent.

struct {
union {
char _member[sizeof(UnknownFunctionMember_t)];
UnknownFunctionMember_t _alignementAndSizeGuarantees;
};
void * _object;
} _method;
Copy link
Contributor

Choose a reason for hiding this comment

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

can we have a better name than _method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could call it something else, but it really is a method pointer. It points to a specific method on a specific object. I'm open to suggestions for other names, but so far, _method seems right to me.

Copy link
Member

Choose a reason for hiding this comment

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

It is a function member (method doesn't exist in c++ wording) bound to an instance maybe _bound_member_function.

Copy link
Contributor

Choose a reason for hiding this comment

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

let's at least call it _method_pointer

char _raw_storage[sizeof(void*) + sizeof(UnknownFunctionMember_t) + I];
void * _external_functor;
};
};

template<size_t I = 0>
class FunctionPointerSize0 {
public:
FunctionPointerStorage<I> _fp;
virtual void nullmethod()=0;
};

template<size_t I = 0>
class FunctionPointerSize : public FunctionPointerSize0<I> {
public:
void nullmethod() {}
Copy link
Contributor

Choose a reason for hiding this comment

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

can you make explicit why you need nullmethod?

FunctionPointerSize(){}
FunctionPointerSize(const FunctionPointerSize &){}
Copy link
Contributor

Choose a reason for hiding this comment

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

can we simply provide a constexpr member called size (or something line an enum { size = ... }; to make it explicit that we're interested in the size of this class? it could replace sizeof(FunctionPointerSize) further on.

};
template<class T>
Copy link
Member

Choose a reason for hiding this comment

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

If move and forward are not available on ARM CC, it would be nice if these utilities foes into their own header / or better, in compiler pollyfill, if applicable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. They will start their lives here, but will be moved into compiler polyfill as and when that is possible.

T&& forward(typename std::remove_reference<T>::type& a) noexcept
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it useful to use noexcept, since we're not using exception anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably not.

Copy link
Member

Choose a reason for hiding this comment

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

std::forward is not tagged as noexcept, maybe it can be good to follow the standard.

{
return static_cast<T&&>(a);
}
template<class T>
T&& forward(typename std::remove_reference<T>::type&& a) noexcept
{
return static_cast<T&&>(a);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't your forward similar to what std::move does ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's closer to what std::forward does. This implementation is what armcc's documentation recommends: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0472k/chr1407404265784.html

template <typename FunctionType, size_t I = 0>
class FunctionPointerInterface;

template<typename FunctionType, size_t I = 0>
class StaticPointer;

template<typename FunctionType, typename C, size_t I = 0>
class MethodPointer;

template<typename FunctionType, typename F, size_t I = 0>
class FunctorPointer;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps add a comment here to indicate the end of the namespace impl.


template <typename OutputSignature, typename InputSignature, size_t I>
class FPFunctor;

template<typename FunctionType, size_t I = 0>
class FunctionPointer;


#include "impl/FP0.hpp"
#include "impl/FP1.hpp"
} // namespace util
} // namespace mbed

#endif
238 changes: 238 additions & 0 deletions core-util/v2/impl/FP0.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/* mbed Microcontroller Library
* Copyright (c) 2006-2015 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __FUNCTIONPOINTER_IMPL_FP0_HPP__
#define __FUNCTIONPOINTER_IMPL_FP0_HPP__
#ifndef __FUNCTIONPOINTER_HPP__
#error FP0.hpp is an internal file that should only be called from inside FunctionPointer.hpp
#endif

template<typename R, size_t I>
class FunctionPointer<R(), I> {
public:
typedef struct arg_struct{
} ArgStruct;
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the need for ArgStruct?

typedef R(*static_fp)(void);

FunctionPointer(R (*fp)()) {
new(_storage) impl::StaticPointer<R(), I>(fp);
}
template <typename C>
FunctionPointer(C *c, R (C::*member)()) {
new(_storage) impl::MethodPointer<R(), C, I>(c, member);
}
template <typename F>
FunctionPointer(const F & f) {
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to add the move constructor ? It will not get generated automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

new(_storage) impl::FunctorPointer<R(), F, I>(f);
}
operator bool() const {
Copy link
Member

Choose a reason for hiding this comment

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

It would be safer to make this conversion explicit, like std::function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

return *reinterpret_cast<impl::FunctionPointerInterface<R()> *>(&_storage);
}
bool operator ==(const FunctionPointer<R()> & rhs) const {
Copy link
Member

Choose a reason for hiding this comment

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

it would be nice to add operator !=

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1

return *reinterpret_cast<impl::FunctionPointerInterface<R()> *>(&_storage) ==
Copy link
Member

Choose a reason for hiding this comment

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

The comparison is not accurate because it allow to compare carrots and potatoes. But it is a difficult problem to solve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unless there is a way to compare to instantiations of a base class and validate that they have the same vtable pointer, I'm not sure this is solvable.

Copy link
Member

Choose a reason for hiding this comment

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

Using external vtable instead of inheritance, it is possible to make a valid operator== but the complexity will increase by an order of magnitude for peoples non initiated.

(
Another advantage of external vtable is the reduction of template instantiation:
Today, each function pointer instantiate the following functions:

  • virtual operator bool() const = 0;
  • virtual bool operator ==(const FunctionPointerInterface &) const = 0;
  • virtual R call(void) const = 0;
  • virtual ~FunctionPointerInterface(){};
  • virtual void copy_to(void *) const = 0;

With an external vtable, we can control more finely template instantiations and it became possible to instantiate:

  • virtual operator bool() const = 0 only one instantiation for all POD
  • virtual bool operator ==(const FunctionPointerInterface &) const only one instance for all POD
  • virtual ~FunctionPointerInterface() only one instance for all POD
  • virtual void copy_to(void *) const only one instance for all POD
    )

Copy link
Contributor

Choose a reason for hiding this comment

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

can we use if (typeid(*this) != typeid(other)) return false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't that require RTTI?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, you're right. I take that back.

*reinterpret_cast<impl::FunctionPointerInterface<R()> *>(rhs.storage);
}
FunctionPointer<R()> & operator=(const FunctionPointer<R()> & rhs) {
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to add the move assignment operator ? It will not get generated automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

reinterpret_cast<impl::FunctionPointerInterface<R()>*>(_storage)->~FunctionPointerInterface<R()>();
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think I understand the semantics here. Why does operator = call a destructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you assign to a function pointer, you must clear out the contents of the internal storage before installing something new into the storage. The storage is always populated using new(_storage) FPDerivative() so it must always be destroyed properly.

rhs.copy_to(_storage);
return *this;
}
inline R operator ()() const {
return call();
}
R call() const {
return reinterpret_cast<impl::FunctionPointerInterface<R()>*>(_storage)->call();
}
~FunctionPointer() {
reinterpret_cast<impl::FunctionPointerInterface<R()>*>(_storage)->~FunctionPointerInterface<R()>();
}
protected:
union {
char _storage[sizeof(impl::FunctionPointerSize<I>)];
impl::FunctionPointerSize<I> _alignementAndSizeGuarantees;
};
};

namespace impl {
template <typename R, size_t I>
class FunctionPointerInterface<R(),I>{
public:
virtual operator bool() const = 0;
virtual bool operator ==(const FunctionPointerInterface &) const = 0;
virtual R call(void) const = 0;
virtual ~FunctionPointerInterface(){};
virtual void copy_to(void *) const = 0;
protected:
FunctionPointerStorage<I> _fp;
};

/** A class for storing and calling a pointer to a static or member void function without arguments
*/
template<typename R, size_t I>
class StaticPointer<R(), I> : public FunctionPointerInterface<R(), I>{
public:
StaticPointer(R (*function)() = 0)
{
attach(function);
}
StaticPointer(const StaticPointer& fp) {
*this = fp;
}
void attach(R (*function)()) {
this->_fp._static_fp = (void*)function;
}
R call() const{
return reinterpret_cast<R (*)()>(this->_fp._static_fp)();
}
R (*get_function() const)() {
return this->_fp._static_fp;
}
operator bool() const {
return (this->_fp._static_fp != NULL);
}
bool operator==(const FunctionPointerInterface<R()> & rhs) const {
return (this->_fp._static_fp == static_cast<const StaticPointer *>(&rhs)->_fp._static_fp);
}
StaticPointer & operator = (const StaticPointer & rhs) {
this->_fp._static_fp = rhs._fp._static_fp;
return *this;
}
void copy_to(void * storage) const {
new(storage) StaticPointer(*this);
}
};

template<typename R, typename C, size_t I>
class MethodPointer<R(), C, I> : public FunctionPointerInterface<R(), I>{
public:
MethodPointer(C *object, R (C::*member)(void))
{
attach(object, member);
}
MethodPointer(const MethodPointer& fp) {
*this = fp;
}
void attach(C *object, R (C::*member)(void)) {
this->_fp._method._object = (void *) object;
*reinterpret_cast<R (C::**)(void)>(this->_fp._method._member) = member;
}
R call() const{
C* o = static_cast<C*>(this->_fp._method._object);
R (C::**m)(void) = reinterpret_cast<R (C::**)(void)>(const_cast<char *>(this->_fp._method._member));
return (o->**m)();
}
R (*get_function() const)(){
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do without having this method for MethodPointer and others (except StaticPointer)? else if it is generic, it should be in FunctionPointerInterface

return NULL;
}
operator bool() const {
return (this->_fp._method._object != NULL);
}
bool operator ==(const FunctionPointerInterface<R()> & rhs) const {
return (this->_fp._method._object == static_cast<const MethodPointer *>(&rhs)->_fp._method._object) &&
(0 == memcmp(this->_fp._method._member, static_cast<const MethodPointer *>(&rhs)->_fp._method._member, sizeof(this->_fp._method._member)));
}
MethodPointer & operator = (const MethodPointer & rhs) {
this->_fp._method._object = rhs._fp._method._object;
memcpy(this->_fp._method._member, rhs._fp._method._member, sizeof(this->_fp._method._member));
return *this;
}
void copy_to(void * storage) const {
new(storage) MethodPointer(*this);
}
};



template<typename R, typename F, size_t I>
class FunctorPointer<R(), F, I> : public FunctionPointerInterface<R(), I>{
constexpr static const bool Internal = sizeof(F) <= sizeof(impl::FunctionPointerStorage<I>);
public:
FunctorPointer(const F & f)
{
attach(f);
}
FunctorPointer(const FunctorPointer & fp)
{
*this = fp;
}
template <typename T = F>
typename std::enable_if<Internal && std::is_same<T,F>::value>::type
attach(const F & f)
{
new(this->_fp._raw_storage)F(f);
}
template <typename T = F>
typename std::enable_if<!Internal && std::is_same<T,F>::value>::type
attach(const F & f) {
this->_fp._external_functor = new F(f);
}
R call() const{
return const_ref()();
}
R (*get_function() const)(){
return NULL;
}
operator bool() const {
return true;
}
bool operator ==(const FunctionPointerInterface<R(), I> & rhs) const {
return false;
}
FunctorPointer & operator = (const FunctorPointer & rhs) {
deallocate();
attach(*reinterpret_cast<const F *>(rhs._fp._raw_storage));
return *this;
}
~FunctorPointer() {
deallocate();
}
void copy_to(void * storage) const {
new(storage) FunctorPointer(*this);
}
protected:
template <typename T = F>
typename std::enable_if<Internal && std::is_same<T,F>::value, const F &>::type
const_ref() const{
return *reinterpret_cast<const F *>(this->_fp._raw_storage);
}
template <typename T = F>
typename std::enable_if<Internal && std::is_same<T,F>::value, F &>::type
ref() {
return *reinterpret_cast<F *>(this->_fp._raw_storage);
}
template <typename T = F>
typename std::enable_if<Internal && std::is_same<T,F>::value>::type
deallocate() {
reinterpret_cast<F*>(this->_fp._raw_storage)->~F();
}

template <typename T = F>
typename std::enable_if<!Internal && std::is_same<T,F>::value, const F &>::type
const_ref() const{
return *reinterpret_cast<const F *>(this->_fp._external_functor);
}
template <typename T = F>
typename std::enable_if<!Internal && std::is_same<T,F>::value, F &>::type
ref() {
return *reinterpret_cast<F *>(this->_fp._external_functor);
}
template <typename T = F>
typename std::enable_if<!Internal && std::is_same<T,F>::value>::type
deallocate() {
reinterpret_cast<F*>(this->_fp._external_functor)->~F();
}
};
}
#endif
Loading