//! A value-wrapping mutex / rw-lock #pragma once #include #include #include #include #include namespace fx::mutex { template concept Delegate = std::is_invocable_v, T>; template struct storage_traits { using value_type = std::decay_t; typedef std::unique_ptr storage_type; template requires(std::is_constructible_v) constexpr static storage_type make_storage(Args&&... args) noexcept(std::is_nothrow_constructible_v) { return std::make_unique(std::forward(args)...); } constexpr static storage_type make_storage() noexcept(std::is_nothrow_default_constructible_v) requires(std::is_default_constructible_v) { return std::make_unique(); } }; namespace details [[gnu::visibility("hidden")]] { template concept has_storage = requires{ typename storage_traits::storage_type; }; template concept has_valid_storage = has_storage and requires(storage_traits::storage_type const& value) { { value.get() } noexcept -> std::convertible_to< typename std::add_pointer_t::value_type> >; }; template concept can_construct = has_storage and requires(Args&&... args) { { storage_traits::template make_storage(std::forward(args)...) } -> std::convertible_to< typename storage_traits::storage_type >; }; template concept can_default_construct = has_storage and requires{ { storage_traits::make_storage() } -> std::convertible_to< typename storage_traits::storage_type >; }; } template class Mutex { static_assert(details::has_valid_storage, "Invalid storage_traits spec for stored type"); using value_type = storage_traits::value_type; using storage_type = storage_traits::storage_type; storage_type m_value; public: using mutex_type = std::decay_t; constexpr explicit Mutex(std::convertible_to auto&& up) noexcept : m_value(std::move(up)) {} constexpr explicit Mutex() noexcept requires(details::can_default_construct) : m_value(storage_traits::make_storage()) {} constexpr Mutex(T&& value) noexcept(std::is_nothrow_move_constructible_v) : m_value(storage_traits::make_storage(std::forward(value))) //, m_mutex(std::forward(mutex)) {} Mutex(const Mutex&) = delete; Mutex(Mutex&&) = delete; Mutex& operator=(Mutex const&) = delete; Mutex& operator=(Mutex &&) = delete; [[gnu::returns_nonnull, gnu::artificial]] inline T* get_ptr_unsafe() const noexcept { return get_raw_ptr(); } [[gnu::returns_nonnull, gnu::artificial]] inline T const* get_ptr() const noexcept { return static_cast(get_raw_ptr()); } inline virtual mutex_type& get_mutex() const noexcept { return m_mutex; } protected: [[gnu::returns_nonnull]] inline virtual value_type* get_raw_ptr() const noexcept { value_type* p = m_value.get(); if(!p) __builtin_unreachable(); return p; } inline T& value() & noexcept { return *get_raw_ptr(); } inline T const& value() const& noexcept { return *get_raw_ptr(); } inline T&& value() && noexcept { return std::move(*get_raw_ptr()); } inline T const&& value() const&& noexcept { return std::move(*get_raw_ptr()); } mutable mutex_type m_mutex{}; }; /// For callback passed to `with_lock()` & like functions that copies & moves the value of type `T`. template constexpr auto copy_value(T const& v) noexcept(std::is_nothrow_copy_constructible_v) { return v; } template constexpr auto move_value(T& v) noexcept(std::is_nothrow_move_constructible_v) { return std::move(v); } /// For callback passed to `with_lock()` & like functions that does nothing and returns `void`. [[gnu::artificial, gnu::always_inline]] constexpr void nothing(const auto&) noexcept {} [[gnu::artificial, gnu::always_inline]] constexpr void nothing(auto&) noexcept {} /// Creates a callback for `with_lock()` & like functions that returns an already given value of type `T`. /// /// If `T` is not trivially-copy-constructible, the value returned from the callback will be move-constructed from the captured `value`. /// `value` is moved into the callback via a forward to a decayed `U`. /// If the wish is to pass the rvalue-reference down the chain, having `with_lock()` return the moved value directly, use `return_ref()` instead. template U = T> constexpr auto return_value(U&& value) noexcept(std::is_nothrow_convertible_v) { if constexpr(std::is_trivially_copy_constructible_v) { return [value = std::forward(value)] (auto const&) noexcept(std::is_nothrow_copy_constructible_v) -> T { return value; }; } else return [value = std::forward(value)] (auto const&) mutable noexcept(std::is_nothrow_move_constructible_v) -> T { return std::move(value); }; } /// Creates a callback for `with_lock()` & like functions that passes a value *through* the callback and constructs it when the callback returns. /// So an rvalue-reference given to `.with_lock(return_ref(std::move(obj)))` will construct the object `T` from a forwarding-reference of the passed rvalue-reference `std::move(obj)` only at the *end* of `.with_lock()` when it returns before releasing the lock.) /// /// This is in contrast to `return_value()`, which captures the passed forwarding-reference *by value* in the callback itself and moves out of it when the callback returns, so the above example would construct `T` from an intermediary `U` which is the decayed-type of the expression `std::move(obj)`. T /// herefore a potential 2nd construction is avoided with this method, however the lifetime of `value` must be taken into account when using this callback, as `value` is captured only by forwarding its reference. /// /// Unlike `return_value()`, The lifetime of the returned callback is bound to `U`. template U = T> constexpr auto return_ref(U&& value) noexcept { return [&] (auto const&) noexcept(std::is_nothrow_convertible_v) -> T { return std::forward(value); }; } } namespace fx { template struct Mutex : public mutex::Mutex { using mutex::Mutex::Mutex; template F> inline decltype(auto) with_lock(F&& f) & noexcept(std::is_nothrow_invocable_v) { std::unique_lock ul{ this->get_mutex() }; return std::forward(f)(this->value()); } template F> inline decltype(auto) with_lock(F&& f) const& noexcept(std::is_nothrow_invocable_v) { std::unique_lock ul{ this->get_mutex() }; return std::forward(f)(this->value()); } template F> inline decltype(auto) with_lock(F&& f) && noexcept(std::is_nothrow_invocable_v) { // Assume this is owned by caller. No lock required. //std::unique_lock ul{ this->get_mutex() }; return std::forward(f)(std::move(this->value())); } template F> inline decltype(auto) with_lock(F&& f) const&& noexcept(std::is_nothrow_invocable_v) { std::unique_lock ul{ this->get_mutex() }; return std::forward(f)(std::move(this->value())); } template F> inline decltype(auto) with_unique_lock(F&& f) const noexcept(std::is_nothrow_invocable_v) { std::unique_lock ul{ this->get_mutex() }; if constexpr(std::is_invocable_v) { return std::forward(f)(*this->get_raw_ptr(), ul); } else return std::forward(f)(*this->get_raw_ptr()); } }; namespace shared { template struct Mutex : public mutex::Mutex { using mutex::Mutex::Mutex; template F> inline decltype(auto) with_lock(F&& f) & noexcept(std::is_nothrow_invocable_v) { std::lock_guard ul{ this->get_mutex() }; return std::forward(f)(this->value()); } template F> inline decltype(auto) with_lock(F&& f) const& noexcept(std::is_nothrow_invocable_v) { std::shared_lock ul{ this->get_mutex() }; return std::forward(f)(this->value()); } template F> inline decltype(auto) with_lock(F&& f) && noexcept(std::is_nothrow_invocable_v) { //std::shared_lock ul{ this->get_mutex() }; return std::forward(f)(std::move(this->value())); } template F> inline decltype(auto) with_lock(F&& f) const&& noexcept(std::is_nothrow_invocable_v) { std::shared_lock ul{ this->get_mutex() }; return std::forward(f)(std::move(this->value())); } template F> inline decltype(auto) with_unique_lock(F&& f) const noexcept(std::is_nothrow_invocable_v) { std::unique_lock ul{ this->get_mutex() }; if constexpr(std::is_invocable_v) { return std::forward(f)(*this->get_raw_ptr(), ul); } else return std::forward(f)(*this->get_raw_ptr()); } template F> inline decltype(auto) with_shared_lock(F&& f) const noexcept(std::is_nothrow_invocable_v) { std::shared_lock ul{ this->get_mutex() }; if constexpr(std::is_invocable_v) { return std::forward(f)(*this->get_raw_ptr(), ul); } else return std::forward(f)(*this->get_raw_ptr()); } }; } template using SharedMutex = shared::Mutex; #if 0 template class Mutex { using value_type = std::decay_t; mutable value_type m_value; protected: std::mutex m_mutex{}; // TODO: Factor this out into template parameter to allow for `SharedMutex` (or `shared::Mutex`) e.g as partial-spec for / inheritance from `fx::mutex::Wrapped` public: constexpr Mutex() noexcept requires(std::is_default_constructible_v) : m_value{} {} constexpr Mutex(T&& value) noexcept : m_value{std::forward(value)} {} Mutex(const Mutex&) = delete; Mutex& operator=( Mutex(Mutex&&) = default; Mutex }; namespace shared { template #error class Mutex /* See above TODO about dual-impl */ } template using SharedMutex = shared::Mutex; #endif }