forked from eclipse-iceoryx/iceoryx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
iox-eclipse-iceoryx#391 initial prototype of std::function replacement
Signed-off-by: Matthias Killat <matthias.killat@apex.ai>
- Loading branch information
1 parent
3c43d88
commit bf821e2
Showing
3 changed files
with
520 additions
and
0 deletions.
There are no files selected for viewing
288 changes: 288 additions & 0 deletions
288
iceoryx_utils/include/iceoryx_utils/internal/cxx/function.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
// Copyright (c) 2020 by Apex.AI Inc. All rights reserved. | ||
// | ||
// 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 IOX_UTILS_FUNCTION_HPP | ||
#define IOX_UTILS_FUNCTION_HPP | ||
|
||
#include "iceoryx_utils/cxx/function_ref.hpp" | ||
#include "iceoryx_utils/cxx/type_traits.hpp" | ||
#include "iceoryx_utils/internal/cxx/storage.hpp" | ||
|
||
#include <type_traits> | ||
#include <utility> | ||
|
||
namespace iox | ||
{ | ||
namespace cxx | ||
{ | ||
template <typename ReturnType, typename... Args> | ||
using signature = ReturnType(Args...); | ||
|
||
template <typename StorageType, typename T> | ||
class storable_function; | ||
|
||
template <typename StorageType, typename ReturnType, typename... Args> | ||
class storable_function<StorageType, signature<ReturnType, Args...>> | ||
{ | ||
private: | ||
///@todo: move to detail namespace, move to inl once complete | ||
|
||
// note that this vtable or a similar approach with virtual is needed to ensure we perform the correct | ||
// operation with the underlying (erased) type | ||
// this means storable_function cannot be used where pointers become invalid, e.g. across process boundaries | ||
struct vtable | ||
{ | ||
// exposing those is intentional, it is an internal structure | ||
void (*copyFunction)(const storable_function& src, storable_function& dest){nullptr}; | ||
void (*moveFunction)(storable_function& src, storable_function& dest){nullptr}; | ||
void (*destroyFunction)(storable_function& f){nullptr}; | ||
|
||
vtable() = default; | ||
vtable(const vtable& other) = default; | ||
vtable& operator=(const vtable& other) = default; | ||
vtable(vtable&& other) = default; | ||
vtable& operator=(vtable&& other) = default; | ||
|
||
void copy(const storable_function& src, storable_function& dest) | ||
{ | ||
if (copyFunction) | ||
{ | ||
copyFunction(src, dest); | ||
} | ||
} | ||
|
||
void move(storable_function& src, storable_function& dest) | ||
{ | ||
if (moveFunction) | ||
{ | ||
moveFunction(src, dest); | ||
} | ||
} | ||
|
||
void destroy(storable_function& f) | ||
{ | ||
if (destroyFunction) | ||
{ | ||
destroyFunction(f); | ||
} | ||
} | ||
}; | ||
|
||
public: | ||
storable_function() = default; | ||
|
||
/// @brief construct from functor (including lambda) | ||
template <typename Functor, | ||
typename = typename std::enable_if<std::is_class<Functor>::value | ||
&& has_signature<Functor, ReturnType, Args...>::value, | ||
void>::type> | ||
storable_function(const Functor& functor) noexcept | ||
{ | ||
storeFunctor(functor); | ||
} | ||
|
||
/// @brief construct from function pointer (including static functions) | ||
storable_function(ReturnType (*function)(Args...)) noexcept | ||
{ | ||
// no need to store anything else (m_storage is not needed here) | ||
m_function = function; | ||
m_storedObj = nullptr; | ||
} | ||
|
||
/// @brief construct from object reference and member function | ||
/// only a pointer to the object is stored for the call | ||
template <typename T, typename = typename std::enable_if<std::is_class<T>::value, void>::type> | ||
storable_function(T& object, ReturnType (T::*method)(Args...)) noexcept | ||
{ | ||
auto p = &object; | ||
auto functor = [p, method](Args... args) -> ReturnType { return (*p.*method)(std::forward<Args>(args)...); }; | ||
storeFunctor(functor); | ||
} | ||
|
||
/// @brief construct from object reference and const member function | ||
/// only a pointer to the object is stored for the call | ||
template <typename T, typename = typename std::enable_if<std::is_class<T>::value, void>::type> | ||
storable_function(const T& object, ReturnType (T::*method)(Args...) const) noexcept | ||
{ | ||
auto p = &object; | ||
auto functor = [p, method](Args... args) -> ReturnType { return (*p.*method)(std::forward<Args>(args)...); }; | ||
storeFunctor(functor); | ||
} | ||
|
||
/// @brief copy construct a function | ||
storable_function(const storable_function& other) noexcept | ||
: m_vtable(other.m_vtable) | ||
{ | ||
m_vtable.copy(other, *this); | ||
} | ||
|
||
/// @brief move construct a function | ||
storable_function(storable_function&& other) noexcept | ||
: m_vtable(other.m_vtable) | ||
{ | ||
m_vtable.move(other, *this); | ||
} | ||
|
||
/// @brief copy assign a function | ||
storable_function& operator=(const storable_function& rhs) noexcept | ||
{ | ||
if (&rhs != this) | ||
{ | ||
m_vtable = rhs.m_vtable; | ||
m_vtable.destroy(*this); | ||
m_vtable.copy(rhs, *this); | ||
} | ||
|
||
return *this; | ||
} | ||
|
||
/// @brief move assign a function | ||
storable_function& operator=(storable_function&& rhs) noexcept | ||
{ | ||
if (&rhs != this) | ||
{ | ||
m_vtable = rhs.m_vtable; | ||
m_vtable.destroy(*this); | ||
m_vtable.move(rhs, *this); | ||
} | ||
|
||
return *this; | ||
} | ||
|
||
/// @brief destroy the function | ||
~storable_function() noexcept | ||
{ | ||
m_vtable.destroy(*this); | ||
} | ||
|
||
/// @brief invoke the stored function | ||
// todo: think about constness, calling it may change the stored object | ||
ReturnType operator()(Args... args) | ||
{ | ||
return m_function(std::forward<Args>(args)...); | ||
} | ||
|
||
/// @brief indicates whether a function was stored | ||
operator bool() noexcept | ||
{ | ||
return m_function.operator bool(); | ||
} | ||
|
||
/// @brief swap two functions | ||
void swap(storable_function& f, storable_function& g) noexcept | ||
{ | ||
storable_function tmp = std::move(f); | ||
f = std::move(g); | ||
g = std::move(tmp); | ||
} | ||
|
||
private: | ||
/// @note in general we cannot know the alignment of the type we want to store at construction time | ||
StorageType m_storage; | ||
void* m_storedObj{nullptr}; | ||
function_ref<signature<ReturnType, Args...>> m_function; | ||
vtable m_vtable; | ||
|
||
template <typename Functor, | ||
typename = typename std::enable_if<std::is_class<Functor>::value | ||
&& has_signature<Functor, ReturnType, Args...>::value, | ||
void>::type> | ||
void storeFunctor(const Functor& functor) noexcept | ||
{ | ||
///@todo we may need something more to get the correct type in general... | ||
using StoredType = typename std::remove_reference<Functor>::type; | ||
auto p = m_storage.template allocate<StoredType>(); | ||
|
||
if (p) | ||
{ | ||
// functor will fit, copy it | ||
new (p) StoredType(functor); | ||
|
||
// erase the functor type and store as reference to the call in storage | ||
m_function = *p; | ||
m_storedObj = p; | ||
m_vtable.copyFunction = copy<StoredType>; | ||
m_vtable.moveFunction = move<StoredType>; | ||
m_vtable.destroyFunction = destroy<StoredType>; | ||
} | ||
// else handle problem, but there should be none when we have the heap fallback | ||
// otherwise we need to detect the size problem at compile time | ||
} | ||
|
||
// need these templates to preserve the actual type T for the underlying copy/move etc. call | ||
template <typename T> | ||
static void copy(const storable_function& src, storable_function& dest) noexcept | ||
{ | ||
if (!src.m_storedObj) | ||
{ | ||
dest.m_storedObj = nullptr; | ||
dest.m_function = src.m_function; | ||
return; | ||
} | ||
|
||
auto p = dest.m_storage.template allocate<T>(); | ||
|
||
if (p) | ||
{ | ||
auto obj = reinterpret_cast<T*>(src.m_storedObj); | ||
p = new (p) T(*obj); | ||
dest.m_function = *p; | ||
dest.m_storedObj = p; | ||
} | ||
} | ||
|
||
template <typename T> | ||
static void move(storable_function& src, storable_function& dest) noexcept | ||
{ | ||
if (!src.m_storedObj) | ||
{ | ||
dest.m_storedObj = nullptr; | ||
dest.m_function = src.m_function; | ||
return; | ||
} | ||
|
||
auto p = dest.m_storage.template allocate<T>(); | ||
|
||
if (p) | ||
{ | ||
auto obj = reinterpret_cast<T*>(src.m_storedObj); | ||
p = new (p) T(std::move(*obj)); | ||
dest.m_function = *p; | ||
dest.m_storedObj = p; | ||
} | ||
} | ||
|
||
template <typename T> | ||
static void destroy(storable_function& f) noexcept | ||
{ | ||
if (f.m_storedObj) | ||
{ | ||
auto p = static_cast<T*>(f.m_storedObj); | ||
p->~T(); | ||
f.m_storage.deallocate(); | ||
f.m_storedObj = nullptr; | ||
} | ||
} | ||
}; | ||
|
||
// exposed to the user, to set the storage type (and reorder template arguments) | ||
// the storage type must precede the (required) variadic arguments in the internal one | ||
|
||
template <typename Signature, uint64_t Bytes = 128> | ||
using function = storable_function<static_storage<Bytes>, Signature>; | ||
|
||
} // namespace cxx | ||
} // namespace iox | ||
|
||
#endif // IOX_UTILS_FUNCTION_HPP |
Oops, something went wrong.