Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions score/concurrency/locked_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include "score/concurrency/type_traits.h"
#include "score/concurrency/unlock_guard.h"

#include <score/optional.hpp>
Comment thread
sankurm marked this conversation as resolved.

#include <functional>
#include <utility>

namespace score
Expand Down Expand Up @@ -202,6 +205,69 @@ 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 Func,
typename = std::enable_if_t<std::is_invocable_v<Func, LockedPtr&>>,
typename FuncResult = std::invoke_result_t<Func, LockedPtr&>,
Comment thread
sankurm marked this conversation as resolved.
typename = std::enable_if_t<!std::is_void_v<FuncResult>>>
[[nodiscard]] auto transform(Func&& f) & -> score::cpp::optional<FuncResult>
Comment thread
sankurm marked this conversation as resolved.
{
if (ptr_ == nullptr)
{
return score::cpp::nullopt;
}
return std::invoke(std::forward<Func>(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 Func,
typename = std::enable_if_t<std::is_invocable_v<Func, const LockedPtr&>>,
typename FuncResult = std::invoke_result_t<Func, const LockedPtr&>,
typename = std::enable_if_t<!std::is_void_v<FuncResult>>>
[[nodiscard]] auto transform(Func&& f) const& -> score::cpp::optional<FuncResult>
{
if (ptr_ == nullptr)
{
return score::cpp::nullopt;
}
return std::invoke(std::forward<Func>(f), *this);
}

/**
* @brief Applies a callable to this LockedPtr if the pointer is non-null.
* The LockedPtr is moved into the callable by value, transferring lock ownership.
* Returns score::cpp::optional containing the result.
* @tparam Func Callable type that accepts LockedPtr by value and returns a non-void type.
Comment thread
sankurm marked this conversation as resolved.
Outdated
* @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 Func,
typename = std::enable_if_t<std::is_invocable_v<Func, LockedPtr>>,
typename FuncResult = std::invoke_result_t<Func, LockedPtr>,
typename = std::enable_if_t<!std::is_void_v<FuncResult>>>
[[nodiscard]] auto transform(Func&& f) && -> score::cpp::optional<FuncResult>
{
if (ptr_ == nullptr)
{
return score::cpp::nullopt;
}
return std::invoke(std::forward<Func>(f), std::move(*this));
}

/**
* @brief Equality comparison with another LockedPtr.
* @param other The LockedPtr to compare with.
Expand Down
65 changes: 65 additions & 0 deletions score/concurrency/locked_ptr_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "score/concurrency/test_types.h"
#include "score/concurrency/unlock_guard.h"

#include <score/optional.hpp>

#include <functional>
#include <mutex>
#include <shared_mutex>
Expand All @@ -41,6 +43,21 @@ struct IntWrapper
};

using LPtr2IntW = LockedPtr<IntWrapper, std::unique_lock<MockMutex>>;

auto value_by_10 = [](LPtr2IntW& lp) {
return lp->value / 10.0;
};
auto cvalue_by_10 = [](const LPtr2IntW& lp) {
Comment thread
sankurm marked this conversation as resolved.
Outdated
return lp->value / 10.0;
};

auto move_ptr = [](LPtr2IntW&& ptr) {
return std::move(ptr);
};

auto get_obj = [](const LPtr2IntW& ptr) {
return ptr.get();
};
} // namespace

TEST(LockedPtrTest, ConstructionWithTypes)
Expand Down Expand Up @@ -454,4 +471,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(value_by_10), score::cpp::optional{4.2});

// Invocables accepting const lvalue ref
EXPECT_EQ(lockedptr.transform(cvalue_by_10), score::cpp::optional{4.2});

// Invocables accepting const lvalue ref transforming const LockedPtr
EXPECT_EQ(std::as_const(lockedptr).transform(cvalue_by_10), score::cpp::optional{4.2});

auto result = lockedptr.transform(value_by_10);
ASSERT_TRUE((std::is_same_v<decltype(result), score::cpp::optional<double>>))
<< "transform should return score::cpp::optional<double>";
}

TEST(LockedPtrTest, TransformLvalueRefNull)
{
IntWrapper* nullp = nullptr;
MockMutex mut;
auto lp = LockedPtr(nullp, std::unique_lock{mut});

EXPECT_EQ(lp.transform(cvalue_by_10), score::cpp::optional<double>{});
EXPECT_EQ(lp.transform(cvalue_by_10), 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(move_ptr).value().get(), &obj);
}

TEST(LockedPtrTest, TransformRvalueRefNull)
{
IntWrapper* nullp = nullptr;
MockMutex mut;

EXPECT_EQ(LockedPtr(nullp, std::unique_lock{mut}).transform(get_obj), score::cpp::optional<const IntWrapper*>{});
EXPECT_EQ(LockedPtr(nullp, std::unique_lock{mut}).transform(cvalue_by_10), score::cpp::nullopt);
}

} // namespace test
Loading