Luna/libos/include/os/Action.h

224 lines
6.9 KiB
C
Raw Normal View History

2023-10-07 13:31:50 +00:00
/**
* @file Action.h
* @author apio (cloudapio.eu)
* @brief Wrapper for callable objects, like function pointers or lambdas.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Heap.h>
#include <luna/SharedPtr.h>
2023-10-07 13:31:50 +00:00
namespace os
{
2023-10-10 20:11:12 +00:00
/**
* @brief Wrapper for callable objects with no arguments.
2023-10-10 20:11:12 +00:00
*
* Certain callable types such as capture lambdas don't have a named type, thus they can't be passed to functions as
* arguments. The Action type acts as a callable wrapper for such functions, allowing them to be passed as arguments
* to any function.
*/
class Action
{
public:
2023-10-10 20:11:12 +00:00
/**
* @brief Construct a new empty Action object.
*
* Since it is not wrapping any callable object, this object is invalid and attempting to invoke its
* (nonexistent) callable object will result in a crash at runtime.
*/
Action()
{
m_action = nullptr;
}
2023-10-10 20:11:12 +00:00
/**
* @brief Construct a new Action object, moving it from another one.
*
* @param other The old Action object to move from. This object will become invalid.
*/
Action(Action&& other) : m_action(other.m_action)
{
other.m_action = {};
}
/**
* @brief Construct a new Action object, copying it from another one.
*
* @param other The old Action object to copy from.
*/
Action(const Action& other) : m_action(other.m_action)
{
}
2023-10-10 20:11:12 +00:00
/**
* @brief Construct a new Action object from a callable object.
*
* @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
* doesn't actually need a type name.
* @param function The callable object to wrap.
*/
template <typename T> Action(T&& function)
{
m_action =
adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl<T>(move(function))).release_value();
}
2023-10-10 20:11:12 +00:00
/**
* @brief Assign a new callable object to this Action.
*
* @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
* doesn't actually need a type name.
* @param function The callable object to wrap.
* @return Action& A reference to this object, as required by operator=().
*/
template <typename T> Action& operator=(T&& function)
{
m_action =
adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl<T>(move(function))).release_value();
return *this;
}
2023-10-10 20:11:12 +00:00
/**
* @brief Call the underlying object.
*/
void operator()()
{
2023-10-10 20:11:12 +00:00
expect(m_action, "os::Action called with no underlying callable object");
return m_action->call();
}
private:
class ActionBase : public Shareable
{
public:
virtual void call() = 0;
virtual ~ActionBase() = default;
};
template <typename T> class ActionImpl final : public ActionBase
{
public:
ActionImpl(T&& function) : m_function(move(function))
{
}
void call()
{
return m_function();
}
private:
T m_function;
};
SharedPtr<ActionBase> m_action;
};
/**
* @brief Wrapper for callable objects.
*
* Certain callable types such as capture lambdas don't have a named type, thus they can't be passed to functions as
* arguments. The Function type acts as a callable wrapper for such functions, allowing them to be passed as
* arguments to any function.
*/
template <class... Args> class Function
{
public:
/**
* @brief Construct a new empty Function object.
*
* Since it is not wrapping any callable object, this object is invalid and attempting to invoke its
* (nonexistent) callable object will result in a crash at runtime.
*/
Function()
{
m_action = nullptr;
}
/**
* @brief Construct a new Function object, moving it from another one.
*
* @param other The old Function object to move from. This object will become invalid.
*/
Function(Function&& other) : m_action(other.m_action)
{
other.m_action = {};
}
/**
* @brief Construct a new Function object, copying it from another one.
*
* @param other The old Function object to copy from.
*/
Function(const Function& other) : m_action(other.m_action)
{
}
/**
* @brief Construct a new Function object from a callable object.
*
* @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
* doesn't actually need a type name.
* @param function The callable object to wrap.
*/
template <typename T> Function(T&& function)
{
m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl<T>(move(function)))
.release_value();
}
/**
* @brief Assign a new callable object to this Function.
*
* @tparam T The type of the callable object. Since this can be guessed by template deduction, the object
* doesn't actually need a type name.
* @param function The callable object to wrap.
* @return Action& A reference to this object, as required by operator=().
*/
template <typename T> Function& operator=(T&& function)
{
m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl<T>(move(function)))
.release_value();
return *this;
}
/**
* @brief Call the underlying object.
*/
void operator()(Args... args)
{
expect(m_action, "os::Function called with no underlying callable object");
return m_action->call(args...);
}
private:
class FunctionBase : public Shareable
{
public:
virtual void call(Args... args) = 0;
virtual ~FunctionBase() = default;
};
template <typename T> class FunctionImpl final : public FunctionBase
{
public:
FunctionImpl(T&& function) : m_function(move(function))
{
}
void call(Args... args) override
{
return m_function(args...);
}
private:
T m_function;
};
SharedPtr<FunctionBase> m_action;
};
}