| #pragma once |
| #include <cstdlib> |
| #include <optional> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| |
| namespace stdplus |
| { |
| |
| /** @brief An RAII handle which takes an object and calls a user specified |
| * function on the object when it should be cleaned up. |
| * @details This is useful for adding RAII semantics to non-RAII things like |
| * file descriptors, or c structs allocated with special routines. |
| * We could make a simple file descriptor wrapper that is |
| * automatically closed: |
| * |
| * struct CloseFd { |
| * void operator()(int&& fd) { close(fd); } |
| * }; |
| * using Fd = Managed<int>::HandleF<closefd>; |
| * |
| * void some_func() |
| * { |
| * Fd fd(open("somefile", 0)); |
| * char buf[4096]; |
| * int amt = read(*fd, buf, sizeof(buf)); |
| * printf("%.*s\n", amt, data); |
| * } |
| */ |
| template <typename T, typename... As> |
| struct Managed |
| { |
| |
| template <typename Drop> |
| class HandleF |
| { |
| protected: |
| static constexpr bool drop_noexcept = |
| noexcept(Drop()(std::declval<T>(), std::declval<As&>()...)); |
| |
| public: |
| /** @brief Creates a handle owning the object |
| * |
| * @param[in] maybeV - Maybe the object being managed |
| */ |
| template <typename... Vs> |
| constexpr explicit HandleF(std::optional<T>&& maybeV, Vs&&... vs) noexcept( |
| std::is_nothrow_move_constructible_v<std::optional<T>>&& noexcept( |
| std::tuple<As...>(std::declval<Vs>()...))) : |
| as(std::forward<Vs>(vs)...), |
| maybeT(std::move(maybeV)) |
| { |
| } |
| template <typename... Vs> |
| constexpr explicit HandleF(T&& maybeV, Vs&&... vs) noexcept( |
| std::is_nothrow_move_constructible_v<std::optional<T>>&& noexcept( |
| std::tuple<As...>(std::declval<Vs>()...))) : |
| as(std::forward<Vs>(vs)...), |
| maybeT(std::move(maybeV)) |
| { |
| } |
| |
| HandleF(const HandleF& other) = delete; |
| HandleF& operator=(const HandleF& other) = delete; |
| |
| constexpr HandleF(HandleF&& other) noexcept( |
| std::is_nothrow_move_constructible_v<std::tuple<As...>>&& |
| std::is_nothrow_move_constructible_v<std::optional<T>>) : |
| as(std::move(other.as)), |
| maybeT(std::move(other.maybeT)) |
| { |
| other.maybeT = std::nullopt; |
| } |
| |
| constexpr HandleF& operator=(HandleF&& other) noexcept( |
| std::is_nothrow_move_assignable_v<std::tuple<As...>>&& noexcept( |
| std::declval<HandleF>().reset( |
| std::declval<std::optional<T>>()))) |
| { |
| if (this != &other) |
| { |
| reset(std::move(other.maybeT)); |
| as = std::move(other.as); |
| other.maybeT = std::nullopt; |
| } |
| return *this; |
| } |
| |
| virtual ~HandleF() noexcept( |
| std::is_nothrow_destructible_v<std::tuple<As...>>&& |
| std::is_nothrow_destructible_v<std::optional<T>>&& noexcept( |
| std::declval<HandleF>().reset())) |
| { |
| reset(); |
| } |
| |
| /** @brief Gets the managed object |
| * |
| * @return A pointer to the object |
| */ |
| constexpr const T* operator->() const noexcept |
| { |
| return &(*maybeT); |
| } |
| |
| /** @brief Gets the managed object |
| * |
| * @return A reference to the object |
| */ |
| constexpr const T& operator*() const& noexcept |
| { |
| return *maybeT; |
| } |
| |
| /** @brief Determine if we are managing an object |
| * |
| * @return Do we currently have an object |
| */ |
| constexpr explicit operator bool() const noexcept |
| { |
| return static_cast<bool>(maybeT); |
| } |
| |
| /** @brief Determine if we are managing an object |
| * |
| * @return Do we currently have an object |
| */ |
| constexpr bool has_value() const noexcept |
| { |
| return maybeT.has_value(); |
| } |
| |
| /** @brief Gets the managed object |
| * |
| * @throws std::bad_optional_access if it has no object |
| * @return A reference to the managed object |
| */ |
| constexpr const T& value() const& |
| { |
| return maybeT.value(); |
| } |
| |
| /** @brief Gets the managed object if it exists |
| * |
| * @throws std::bad_optional_access if it has no object |
| * @return A reference to the managed object |
| */ |
| constexpr const std::optional<T>& maybe_value() const& noexcept |
| { |
| return maybeT; |
| } |
| |
| /** @brief Resets the managed value to a new value |
| * The container takes ownership of the value |
| * |
| * @param[in] maybeV - Maybe the new value |
| */ |
| constexpr void reset(std::optional<T>&& maybeV) noexcept( |
| drop_noexcept&& std::is_nothrow_move_assignable_v<std::optional<T>>) |
| { |
| maybeDrop(std::index_sequence_for<As...>()); |
| maybeT = std::move(maybeV); |
| } |
| constexpr void reset(T&& maybeV) noexcept( |
| drop_noexcept&& std::is_nothrow_move_assignable_v<std::optional<T>>) |
| { |
| maybeDrop(std::index_sequence_for<As...>()); |
| maybeT = std::move(maybeV); |
| } |
| |
| /** @brief A shorthand reset function for convenience |
| * Same as calling reset(std::nullopt) |
| */ |
| constexpr void reset() noexcept(drop_noexcept) |
| { |
| reset(std::nullopt); |
| } |
| |
| /** @brief Releases the managed value and transfers ownership |
| * to the caller. |
| * |
| * @throws std::bad_optional_access if it has no object |
| * @return The value that was managed |
| */ |
| [[nodiscard]] constexpr T release() |
| { |
| T ret = std::move(maybeT.value()); |
| maybeT = std::nullopt; |
| return ret; |
| } |
| |
| /** @brief Releases the managed value and transfers ownership |
| * to the caller. |
| * |
| * @return Maybe the value that was managed |
| */ |
| [[nodiscard]] constexpr std::optional<T> maybe_release() noexcept |
| { |
| std::optional<T> ret = std::move(maybeT); |
| maybeT = std::nullopt; |
| return ret; |
| } |
| |
| /** @brief Reference the contained data |
| * |
| * @return A reference to the contained data |
| */ |
| constexpr const std::tuple<As...>& data() const noexcept |
| { |
| return as; |
| } |
| constexpr std::tuple<As...>& data() noexcept |
| { |
| return as; |
| } |
| |
| protected: |
| /* Hold the data parameterized for this container */ |
| std::tuple<As...> as; |
| |
| private: |
| /* Stores the managed object if we have one */ |
| std::optional<T> maybeT; |
| |
| template <size_t... Indices> |
| void maybeDrop(std::index_sequence<Indices...>) noexcept(drop_noexcept) |
| { |
| if (maybeT) |
| { |
| Drop()(std::move(*maybeT), std::get<Indices>(as)...); |
| } |
| } |
| }; |
| |
| template <void (*drop)(T&&, As&...)> |
| struct Dropper |
| { |
| void operator()(T&& t, As&... as) noexcept(noexcept(drop)) |
| { |
| drop(std::move(t), as...); |
| } |
| }; |
| |
| template <void (*drop)(T&&, As&...)> |
| using Handle = HandleF<Dropper<drop>>; |
| }; |
| |
| } // namespace stdplus |