diff --git a/score/concurrency/BUILD b/score/concurrency/BUILD index ee4fad7ece..e3468454d3 100644 --- a/score/concurrency/BUILD +++ b/score/concurrency/BUILD @@ -161,6 +161,7 @@ cc_library( ], deps = [ ":unlock_guard", + "@score_baselibs//score/language/futurecpp", ], ) diff --git a/score/concurrency/locked_ptr.h b/score/concurrency/locked_ptr.h index 44e6c55ac6..b9c8d740b9 100644 --- a/score/concurrency/locked_ptr.h +++ b/score/concurrency/locked_ptr.h @@ -16,6 +16,9 @@ #include "score/concurrency/type_traits.h" #include "score/concurrency/unlock_guard.h" +#include + +#include #include namespace score @@ -202,6 +205,72 @@ class LockedPtr return ptr_ != nullptr; } + /** + * @brief Applies a callable to this LockedPtr if the pointer is non-null. + * The callable receives a non-const lvalue reference to this LockedPtr. + * Returns score::cpp::optional containing the result. + * @tparam Func Callable type that accepts LockedPtr& and returns a non-void type. + * @param f The callable to apply. Must not return void. + * @return score::cpp::optional containing the result if non-null, score::cpp::nullopt otherwise. + */ + template >, + typename FuncResult = std::invoke_result_t, + typename = std::enable_if_t>, + std::negation>>>> + [[nodiscard]] auto transform(Func&& f) & -> score::cpp::optional + { + if (ptr_ == nullptr) + { + return score::cpp::nullopt; + } + return std::invoke(std::forward(f), *this); + } + + /** + * @brief Applies a callable to this LockedPtr if the pointer is non-null. + * The callable receives a const lvalue reference to this LockedPtr. + * Returns score::cpp::optional containing the result. + * @tparam Func Callable type that accepts const LockedPtr& and returns a non-void type. + * @param f The callable to apply. Must not return void. + * @return score::cpp::optional containing the result if non-null, score::cpp::nullopt otherwise. + */ + template >, + typename FuncResult = std::invoke_result_t, + typename = std::enable_if_t>, + std::negation>>>> + [[nodiscard]] auto transform(Func&& f) const& -> score::cpp::optional + { + if (ptr_ == nullptr) + { + return score::cpp::nullopt; + } + return std::invoke(std::forward(f), *this); + } + + /** + * @brief Applies a callable to this LockedPtr if the pointer is non-null. + * The LockedPtr is moved into the callable by value allowing the `Func` to take ownership of the LockedPtr. + * Returns score::cpp::optional containing the result. + * @tparam Func Callable type that accepts a LockedPtr by value and returns a non-void type. + * @param f The callable to apply. Must not return void. + * @return score::cpp::optional containing the result if non-null, score::cpp::nullopt otherwise. + */ + template >, + typename FuncResult = std::invoke_result_t, + typename = std::enable_if_t>, + std::negation>>>> + [[nodiscard]] auto transform(Func&& f) && -> score::cpp::optional + { + if (ptr_ == nullptr) + { + return score::cpp::nullopt; + } + return std::invoke(std::forward(f), std::move(*this)); + } + /** * @brief Equality comparison with another LockedPtr. * @param other The LockedPtr to compare with. diff --git a/score/concurrency/locked_ptr_test.cpp b/score/concurrency/locked_ptr_test.cpp index b2604aeee1..aeadd70ab0 100644 --- a/score/concurrency/locked_ptr_test.cpp +++ b/score/concurrency/locked_ptr_test.cpp @@ -18,6 +18,8 @@ #include "score/concurrency/test_types.h" #include "score/concurrency/unlock_guard.h" +#include + #include #include #include @@ -41,6 +43,26 @@ struct IntWrapper }; using LPtr2IntW = LockedPtr>; + +double ValueBy10(LPtr2IntW& lp) +{ + return lp->value / 10.0; +} + +double CValueBy10(const LPtr2IntW& lp) +{ + return lp->value / 10.0; +} + +LPtr2IntW MovePtr(LPtr2IntW&& ptr) +{ + return std::move(ptr); +} + +const IntWrapper* GetObj(const LPtr2IntW& ptr) +{ + return ptr.get(); +} } // namespace TEST(LockedPtrTest, ConstructionWithTypes) @@ -454,4 +476,52 @@ TEST(LockedPtrTest, UnlockGuard) EXPECT_TRUE(mut.is_locked()); } +TEST(LockedPtrTest, TransformLvalueRefNotNull) +{ + IntWrapper obj{42}; + MockMutex mut; + LockedPtr lockedptr(&obj, std::unique_lock{mut}); + + // Invocables accepting lvalue ref + EXPECT_EQ(lockedptr.transform(ValueBy10), score::cpp::optional{4.2}); + + // Invocables accepting const lvalue ref + EXPECT_EQ(lockedptr.transform(CValueBy10), score::cpp::optional{4.2}); + + // Invocables accepting const lvalue ref transforming const LockedPtr + EXPECT_EQ(std::as_const(lockedptr).transform(CValueBy10), score::cpp::optional{4.2}); + + auto result = lockedptr.transform(ValueBy10); + ASSERT_TRUE((std::is_same_v>)) + << "transform should return score::cpp::optional"; +} + +TEST(LockedPtrTest, TransformLvalueRefNull) +{ + IntWrapper* nullp = nullptr; + MockMutex mut; + auto lp = LockedPtr(nullp, std::unique_lock{mut}); + + EXPECT_EQ(lp.transform(CValueBy10), score::cpp::optional{}); + EXPECT_EQ(lp.transform(CValueBy10), score::cpp::nullopt); +} + +TEST(LockedPtrTest, TransformRvalueRefNotNull) +{ + IntWrapper obj{42}; + MockMutex mut; + auto lp = LockedPtr(&obj, std::unique_lock{mut}); + + EXPECT_EQ(std::move(lp).transform(MovePtr).value().get(), &obj); +} + +TEST(LockedPtrTest, TransformRvalueRefNull) +{ + IntWrapper* nullp = nullptr; + MockMutex mut; + + EXPECT_EQ(LockedPtr(nullp, std::unique_lock{mut}).transform(GetObj), score::cpp::optional{}); + EXPECT_EQ(LockedPtr(nullp, std::unique_lock{mut}).transform(CValueBy10), score::cpp::nullopt); +} + } // namespace test