Bug 1885840 - Add support for Maybe::andThen. r=glandium

Differential Revision: https://phabricator.services.mozilla.com/D204997
This commit is contained in:
Andreas Pehrson 2024-03-22 08:17:05 +00:00
Родитель fdd885886a
Коммит 4ceb8a3f25
2 изменённых файлов: 177 добавлений и 0 удалений

Просмотреть файл

@ -9,6 +9,7 @@
#ifndef mozilla_Maybe_h
#define mozilla_Maybe_h
#include <functional>
#include <new> // for placement new
#include <ostream>
#include <type_traits>
@ -288,6 +289,15 @@ struct MaybeStorage<T, true> : MaybeStorageBase<T> {
mIsSome{true} {}
};
template <typename T>
struct IsMaybeImpl : std::false_type {};
template <typename T>
struct IsMaybeImpl<Maybe<T>> : std::true_type {};
template <typename T>
using IsMaybe = IsMaybeImpl<std::decay_t<T>>;
} // namespace detail
template <typename T, typename U = typename std::remove_cv<
@ -665,6 +675,57 @@ class MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe
return Maybe<decltype(std::forward<Func>(aFunc)(extract()))>{};
}
/*
* If |isSome()|, runs the provided function or functor on the contents of
* this Maybe and returns the result. Note that the provided function or
* functor must return a Maybe<U> of any type U.
* If |isNothing()|, returns an empty Maybe value with the same type as what
* the provided function would have returned.
*/
template <typename Func>
constexpr auto andThen(Func&& aFunc) & {
static_assert(std::is_invocable_v<Func, T&>);
using U = std::invoke_result_t<Func, T&>;
static_assert(detail::IsMaybe<U>::value);
if (isSome()) {
return std::invoke(std::forward<Func>(aFunc), ref());
}
return std::remove_cv_t<std::remove_reference_t<U>>{};
}
template <typename Func>
constexpr auto andThen(Func&& aFunc) const& {
static_assert(std::is_invocable_v<Func, const T&>);
using U = std::invoke_result_t<Func, const T&>;
static_assert(detail::IsMaybe<U>::value);
if (isSome()) {
return std::invoke(std::forward<Func>(aFunc), ref());
}
return std::remove_cv_t<std::remove_reference_t<U>>{};
}
template <typename Func>
constexpr auto andThen(Func&& aFunc) && {
static_assert(std::is_invocable_v<Func, T&&>);
using U = std::invoke_result_t<Func, T&&>;
static_assert(detail::IsMaybe<U>::value);
if (isSome()) {
return std::invoke(std::forward<Func>(aFunc), extract());
}
return std::remove_cv_t<std::remove_reference_t<U>>{};
}
template <typename Func>
constexpr auto andThen(Func&& aFunc) const&& {
static_assert(std::is_invocable_v<Func, const T&&>);
using U = std::invoke_result_t<Func, const T&&>;
static_assert(detail::IsMaybe<U>::value);
if (isSome()) {
return std::invoke(std::forward<Func>(aFunc), extract());
}
return std::remove_cv_t<std::remove_reference_t<U>>{};
}
/* If |isSome()|, empties this Maybe and destroys its contents. */
constexpr void reset() {
if (isSome()) {
@ -753,6 +814,17 @@ class Maybe<T&> {
return val;
}
template <typename Func>
constexpr auto andThen(Func&& aFunc) const {
static_assert(std::is_invocable_v<Func, T&>);
using U = std::invoke_result_t<Func, T&>;
static_assert(detail::IsMaybe<U>::value);
if (isSome()) {
return std::invoke(std::forward<Func>(aFunc), ref());
}
return std::remove_cv_t<std::remove_reference_t<U>>{};
}
bool refEquals(const Maybe<T&>& aOther) const {
return mValue == aOther.mValue;
}

Просмотреть файл

@ -1457,6 +1457,110 @@ static bool TestReference() {
return true;
}
static Maybe<int> IncrementAndReturnTag(BasicValue& aValue) {
gFunctionWasApplied = true;
aValue.SetTag(aValue.GetTag() + 1);
return Some(aValue.GetTag());
}
static Maybe<int> AccessValueAndReturnNothing(const BasicValue&) {
gFunctionWasApplied = true;
return Nothing();
}
static Maybe<int> AccessValueAndReturnOther(const BasicValue&) {
gFunctionWasApplied = true;
return Some(42);
}
struct IncrementAndReturnTagFunctor {
IncrementAndReturnTagFunctor() : mBy(1) {}
Maybe<int> operator()(BasicValue& aValue) {
aValue.SetTag(aValue.GetTag() + mBy.GetTag());
return Some(aValue.GetTag());
}
BasicValue mBy;
};
struct AccessValueAndReturnOtherFunctor {
explicit AccessValueAndReturnOtherFunctor(int aVal) : mBy(aVal) {}
Maybe<BasicValue> operator()() {
gFunctionWasApplied = true;
return Some(mBy);
}
BasicValue mBy;
};
static bool TestAndThen() {
// Check that andThen handles the 'Nothing' case.
gFunctionWasApplied = false;
Maybe<BasicValue> mayValue;
Maybe<int> otherValue = mayValue.andThen(&AccessValueAndReturnOther);
MOZ_RELEASE_ASSERT(!gFunctionWasApplied);
MOZ_RELEASE_ASSERT(otherValue.isNothing());
// Check that andThen handles the 'Some' case.
mayValue = Some(BasicValue(1));
otherValue = mayValue.andThen(&AccessValueAndReturnNothing);
MOZ_RELEASE_ASSERT(gFunctionWasApplied);
MOZ_RELEASE_ASSERT(otherValue.isNothing());
gFunctionWasApplied = false;
otherValue = mayValue.andThen(&IncrementAndReturnTag);
MOZ_RELEASE_ASSERT(gFunctionWasApplied);
MOZ_RELEASE_ASSERT(mayValue->GetTag() == 2);
MOZ_RELEASE_ASSERT(*otherValue == 2);
gFunctionWasApplied = false;
otherValue = mayValue.andThen(&AccessValueAndReturnOther);
MOZ_RELEASE_ASSERT(gFunctionWasApplied);
MOZ_RELEASE_ASSERT(*otherValue == 42);
// Check that andThen works with a const reference.
const Maybe<BasicValue>& mayValueCRef = mayValue;
gFunctionWasApplied = false;
otherValue = mayValueCRef.andThen(&AccessValueAndReturnOther);
MOZ_RELEASE_ASSERT(gFunctionWasApplied);
MOZ_RELEASE_ASSERT(*otherValue == 42);
// Check that andThen works with functors.
IncrementAndReturnTagFunctor tagIncrementer;
MOZ_RELEASE_ASSERT(tagIncrementer.mBy.GetStatus() == eWasConstructed);
mayValue = Some(BasicValue(1));
otherValue = mayValue.andThen(tagIncrementer);
MOZ_RELEASE_ASSERT(mayValue->GetTag() == 2);
MOZ_RELEASE_ASSERT(*otherValue == 2);
MOZ_RELEASE_ASSERT(tagIncrementer.mBy.GetStatus() == eWasConstructed);
// Check that andThen works with lambda expressions.
gFunctionWasApplied = false;
mayValue = Some(BasicValue(2));
otherValue = mayValue.andThen(
[](BasicValue& aVal) { return Some(aVal.GetTag() * 2); });
MOZ_RELEASE_ASSERT(*otherValue == 4);
otherValue = otherValue.andThen([](int aVal) { return Some(aVal * 2); });
MOZ_RELEASE_ASSERT(*otherValue == 8);
otherValue = mayValueCRef.andThen([&](const BasicValue& aVal) {
gFunctionWasApplied = true;
return Some(42);
});
MOZ_RELEASE_ASSERT(gFunctionWasApplied == true);
MOZ_RELEASE_ASSERT(*otherValue == 42);
// Check that andThen can move the contained value.
mayValue = Some(BasicValue(1));
otherValue = std::move(mayValue).andThen([&](BasicValue&& aVal) {
BasicValue tmp = std::move(aVal);
return Some(tmp.GetTag());
});
MOZ_RELEASE_ASSERT(mayValue.isNothing());
MOZ_RELEASE_ASSERT(*otherValue == 1);
return true;
}
// These are quasi-implementation details, but we assert them here to prevent
// backsliding to earlier times when Maybe<T> for smaller T took up more space
// than T's alignment required.
@ -1486,6 +1590,7 @@ int main() {
RUN_TEST(TestSomePointerConversion);
RUN_TEST(TestTypeConversion);
RUN_TEST(TestReference);
RUN_TEST(TestAndThen);
return 0;
}