Skip to content

Commit

Permalink
✨ add first function_ref implementation
Browse files Browse the repository at this point in the history
* the current implementation only supports up to one argument
* void return type is currently not fully supported
  • Loading branch information
zie87 committed Oct 13, 2023
1 parent 88d2d5c commit d3cb6f3
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Shortcuts for `function_ref`

We made some shortcuts in the `function_ref` implementation:

* direct call to the callables (no `invoke`), this could mess up the calls for binding
* no specialisation to `invocables` this could end up in ugly compiler errors
* only support for one argument (to much boilerplate)
* void is not proper supported (would need a specialisation)
53 changes: 53 additions & 0 deletions include/zll/functional/detail/bound_entity_type.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifndef ZLL_FUNCTIONAL_DETAIL_BOUNDENTITYTPE_HPP
#define ZLL_FUNCTIONAL_DETAIL_BOUNDENTITYTPE_HPP

#include "zll/meta/traits/enable_if.hpp"
#include "zll/meta/traits/is_const.hpp"
#include "zll/meta/traits/is_function.hpp"
#include "zll/meta/traits/is_object.hpp"

namespace zll {
namespace detail {

struct bound_entity_object {};
struct bound_entity_function {};

union bound_entity_type {
void* object_ptr;
const void* const_object_ptr;
void (*fn_ptr)();

bound_entity_type() : object_ptr(NULL) {}

template <typename T>
explicit bound_entity_type(T* ptr, bound_entity_object) : object_ptr(ptr) {}

template <typename T>
explicit bound_entity_type(const T* ptr, bound_entity_object) : const_object_ptr(ptr) {}

template <typename T>
explicit bound_entity_type(T* ptr, bound_entity_function) : fn_ptr(reinterpret_cast<void (*)()>(ptr)) {}
};

template <typename T>
static typename zll::meta::enable_if<zll::meta::is_object<T>::value && zll::meta::is_const<T>::value, T*>::type get(
bound_entity_type entity) {
return static_cast<T*>(entity.const_object_ptr);
}

template <typename T>
static typename zll::meta::enable_if<zll::meta::is_object<T>::value && !(zll::meta::is_const<T>::value), T*>::type get(
bound_entity_type entity) {
return static_cast<T*>(entity.object_ptr);
}

template <typename T>
static typename zll::meta::enable_if<!zll::meta::is_object<T>::value && (zll::meta::is_function<T>::value), T*>::type
get(bound_entity_type entity) {
return reinterpret_cast<T*>(entity.fn_ptr);
}

} // namespace detail
} // namespace zll

#endif // ZLL_FUNCTIONAL_DETAIL_BOUNDENTITYTPE_HPP
18 changes: 18 additions & 0 deletions include/zll/functional/detail/fn_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef ZLL_FUNCTIONAL_DETAIL_FNTYPES_HPP
#define ZLL_FUNCTIONAL_DETAIL_FNTYPES_HPP

namespace zll {
namespace detail {
// clang-format off
template <typename> struct fn_types;

template <typename R> struct fn_types<R()> { typedef R type(); };
template <typename R> struct fn_types<R() const> { typedef R type(); };

template <typename R, typename T> struct fn_types<R(T)> { typedef R type(T); };
template <typename R, typename T> struct fn_types<R(T) const> { typedef R type(T); };
// clang-format on
} // namespace detail
} // namespace zll

#endif // ZLL_FUNCTIONAL_DETAIL_FNTYPES_HPP
153 changes: 153 additions & 0 deletions include/zll/functional/function_ref.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#ifndef ZLL_FUNCTIONAL_FUNCTIONREF_HPP
#define ZLL_FUNCTIONAL_FUNCTIONREF_HPP

#include "zll/functional/detail/bound_entity_type.hpp"
#include "zll/functional/detail/fn_types.hpp"

#include "zll/memory/address_of.hpp"

#include "zll/meta/traits/enable_if.hpp"
#include "zll/meta/traits/is_object.hpp"
#include "zll/meta/traits/is_function.hpp"
#include "zll/meta/traits/remove_reference.hpp"

#include "zll/utils/nontype.hpp"

namespace zll {
namespace detail {

template <typename T, typename U>
struct fn_enable_if_object {
typedef typename zll::meta::enable_if<zll::meta::is_object<T>::value, U>::type type;
};

template <typename T, typename U>
struct fn_enable_if_function {
typedef typename zll::meta::enable_if<zll::meta::is_function<T>::value, U>::type type;
};

} // namespace detail

template <typename SIG_T, typename = typename detail::fn_types<SIG_T>::type>
class function_ref;

template <typename SIG_T, typename R>
class function_ref<SIG_T, R()> {
typedef detail::bound_entity_type entity_type;
typedef R (*thunk_ptr_type)(entity_type);

public:
typedef R (*signature_type)();

template <typename F>
explicit function_ref(
const F& f, typename zll::meta::enable_if<!(zll::meta::is_member_pointer<F>::value ||
zll::is_nontype<typename zll::meta::remove_cvref<F>::type>::value),
void*>::type = NULL)
: m_bound_entity(zll::addressof(f), detail::bound_entity_object()),
m_thunk_ptr(function_ref::thunk_call<typename zll::meta::remove_reference<F>::type>) {}

template <typename F>
explicit function_ref(F* f, typename detail::fn_enable_if_function<F, void*>::type = NULL)
: m_bound_entity(f, detail::bound_entity_function()), m_thunk_ptr(function_ref::thunk_fn_call<F>) {}

template <signature_type VALUE>
function_ref(zll::nontype_t<signature_type, VALUE>)
: m_bound_entity(), m_thunk_ptr(function_ref::thunk_nontype_call<VALUE>) {}

template <typename T, T VALUE, typename U>
function_ref(zll::nontype_t<T, VALUE>, U& obj)
: m_bound_entity(zll::addressof(obj), detail::bound_entity_object()),
m_thunk_ptr(function_ref::thunk_member_call<T, VALUE, U>) {}

R operator()() const { return m_thunk_ptr(m_bound_entity); }

private:
template <typename T>
static typename zll::meta::enable_if<!(zll::is_nontype<T>::value), R>::type thunk_call(entity_type entity) {
T& obj = *detail::get<T>(entity);
return obj();
}

template <typename F>
static R thunk_fn_call(entity_type entity) {
return detail::get<F>(entity)();
}

template <signature_type VALUE>
static R thunk_nontype_call(entity_type) {
return VALUE();
}

template <typename T, T VALUE, typename U>
static R thunk_member_call(entity_type entity) {
U& obj = *detail::get<U>(entity);
return (obj.*VALUE)();
}

entity_type m_bound_entity;
thunk_ptr_type m_thunk_ptr;
};

template <typename SIG_T, typename R, typename ARG1_T>
class function_ref<SIG_T, R(ARG1_T)> {
typedef detail::bound_entity_type entity_type;
typedef R (*thunk_ptr_type)(entity_type, ARG1_T);

public:
typedef R (*signature_type)(ARG1_T);

template <typename F>
explicit function_ref(
const F& f, typename zll::meta::enable_if<!(zll::meta::is_member_pointer<F>::value ||
zll::is_nontype<typename zll::meta::remove_cvref<F>::type>::value),
void*>::type = NULL)
: m_bound_entity(zll::addressof(f), detail::bound_entity_object()),
m_thunk_ptr(function_ref::thunk_call<typename zll::meta::remove_reference<F>::type>) {}

template <typename F>
explicit function_ref(F* f, typename detail::fn_enable_if_function<F, void*>::type = NULL)
: m_bound_entity(f, detail::bound_entity_function()), m_thunk_ptr(function_ref::thunk_fn_call<F>) {}

template <signature_type VALUE>
function_ref(zll::nontype_t<signature_type, VALUE>)
: m_bound_entity(), m_thunk_ptr(function_ref::thunk_nontype_call<VALUE>) {}

template <typename T, T VALUE, typename U>
function_ref(zll::nontype_t<T, VALUE>, U& obj)
: m_bound_entity(zll::addressof(obj), detail::bound_entity_object()),
m_thunk_ptr(function_ref::thunk_member_call<T, VALUE, U>) {}

R operator()(ARG1_T arg) const { return m_thunk_ptr(m_bound_entity, arg); }

private:
template <typename T>
static typename zll::meta::enable_if<!(zll::is_nontype<T>::value), R>::type thunk_call(entity_type entity,
ARG1_T arg) {
T& obj = *detail::get<T>(entity);
return obj(arg);
}

template <typename F>
static R thunk_fn_call(entity_type entity, ARG1_T arg) {
return detail::get<F>(entity)(arg);
}

template <signature_type VALUE>
static R thunk_nontype_call(entity_type, ARG1_T arg) {
return VALUE(arg);
}

template <typename T, T VALUE, typename U>
static R thunk_member_call(entity_type entity, ARG1_T arg) {
U& obj = *detail::get<U>(entity);
return (obj.*VALUE)(arg);
}

entity_type m_bound_entity;
thunk_ptr_type m_thunk_ptr;
};

} // namespace zll

#endif // ZLL_FUNCTIONAL_FUNCTIONREF_HPP
112 changes: 112 additions & 0 deletions tests/unit_tests/functional/function_ref_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include <zll/functional/function_ref.hpp>
#include <zll/utils/typeof_macro.hpp>

#include <utest.h>

struct functor1 {
int operator()() const { return 17; }
};

int free_fun1() { return 42; }

struct static_fun {
static int fun() { return 15; }
};

struct member_fun {
int fun() const { return 3; }
};

UTEST(functional_function_ref, no_parameter) {
{
functor1 fun;
zll::function_ref<int() const> fn_ref(fun);

ASSERT_EQ(fn_ref(), 17);
}
{
const functor1 fun;
zll::function_ref<int() const> fn_ref(fun);

ASSERT_EQ(fn_ref(), 17);
}
{
zll::function_ref<int()> fn_ref(free_fun1);
ASSERT_EQ(fn_ref(), 42);
}
{
zll::function_ref<int()> fn_ref(&free_fun1);
ASSERT_EQ(fn_ref(), 42);
}
{
zll::nontype_t<int (*)(), free_fun1> nontype;
zll::function_ref<int()> fn_ref(nontype);
ASSERT_EQ(fn_ref(), 42);
}
{
zll::nontype_t<int (*)(), static_fun::fun> nontype;
zll::function_ref<int()> fn_ref(nontype);
ASSERT_EQ(fn_ref(), 15);
}
{
zll::nontype_t<ZLL_TYPE_OF(&member_fun::fun), &member_fun::fun> nontype;

member_fun obj;
zll::function_ref<int()> fn_ref(nontype, obj);
ASSERT_EQ(fn_ref(), 3);
}
}

struct functor2 {
int operator()(int a) const { return a; }
};

int free_fun2(char) { return 42; }

struct static_fun2 {
static int fun(int a) { return a; }
};

struct member_fun2 {
int fun(int a) const { return a; }
};

UTEST(functional_function_ref, one_parameter) {
{
functor2 fun;
zll::function_ref<int(int) const> fn_ref(fun);

ASSERT_EQ(fn_ref(5), 5);
}
{
const functor2 fun;
zll::function_ref<int(int) const> fn_ref(fun);

ASSERT_EQ(fn_ref(8), 8);
}
{
zll::function_ref<int(char)> fn_ref(free_fun2);
ASSERT_EQ(fn_ref('a'), 42);
}
{
zll::function_ref<int(char)> fn_ref(&free_fun2);
ASSERT_EQ(fn_ref('f'), 42);
}
{
zll::nontype_t<int (*)(char), free_fun2> nontype;
zll::function_ref<int(char)> fn_ref(nontype);
ASSERT_EQ(fn_ref('g'), 42);
}
{
zll::nontype_t<int (*)(int), static_fun2::fun> nontype;
zll::function_ref<int(int)> fn_ref(nontype);
ASSERT_EQ(fn_ref(11), 11);
}
{
zll::nontype_t<ZLL_TYPE_OF(&member_fun2::fun), &member_fun2::fun> nontype;

member_fun2 obj;
zll::function_ref<int(int)> fn_ref(nontype, obj);
ASSERT_EQ(fn_ref(-12), -12);
}
}
2 changes: 2 additions & 0 deletions tests/unit_tests/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

unit_tests_srcs = [
'functional/function_ref_tests.cpp',

'memory/address_of_tests.cpp',

'meta/integral_constant_tests.cpp',
Expand Down

0 comments on commit d3cb6f3

Please sign in to comment.