/** * @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 #include namespace os { /** * @brief Wrapper for callable objects with no arguments. * * 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: /** * @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; } /** * @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) { } /** * @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 Action(T&& function) { m_action = adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl(move(function))).release_value(); } /** * @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 Action& operator=(T&& function) { m_action = adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl(move(function))).release_value(); return *this; } /** * @brief Call the underlying object. */ void operator()() { 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 class ActionImpl final : public ActionBase { public: ActionImpl(T&& function) : m_function(move(function)) { } void call() { m_function(); } private: T m_function; }; SharedPtr 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 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 Function(T&& function) { m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl(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 Function& operator=(T&& function) { m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl(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 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 m_action; }; }