diff --git a/mfbt/Maybe.h b/mfbt/Maybe.h index ff6f6b6ecb83..859cf23a0fba 100644 --- a/mfbt/Maybe.h +++ b/mfbt/Maybe.h @@ -9,6 +9,7 @@ #ifndef mozilla_Maybe_h #define mozilla_Maybe_h +#include #include // for placement new #include #include @@ -288,6 +289,15 @@ struct MaybeStorage : MaybeStorageBase { mIsSome{true} {} }; +template +struct IsMaybeImpl : std::false_type {}; + +template +struct IsMaybeImpl> : std::true_type {}; + +template +using IsMaybe = IsMaybeImpl>; + } // namespace detail template (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 of any type U. + * If |isNothing()|, returns an empty Maybe value with the same type as what + * the provided function would have returned. + */ + template + constexpr auto andThen(Func&& aFunc) & { + static_assert(std::is_invocable_v); + using U = std::invoke_result_t; + static_assert(detail::IsMaybe::value); + if (isSome()) { + return std::invoke(std::forward(aFunc), ref()); + } + return std::remove_cv_t>{}; + } + + template + constexpr auto andThen(Func&& aFunc) const& { + static_assert(std::is_invocable_v); + using U = std::invoke_result_t; + static_assert(detail::IsMaybe::value); + if (isSome()) { + return std::invoke(std::forward(aFunc), ref()); + } + return std::remove_cv_t>{}; + } + + template + constexpr auto andThen(Func&& aFunc) && { + static_assert(std::is_invocable_v); + using U = std::invoke_result_t; + static_assert(detail::IsMaybe::value); + if (isSome()) { + return std::invoke(std::forward(aFunc), extract()); + } + return std::remove_cv_t>{}; + } + + template + constexpr auto andThen(Func&& aFunc) const&& { + static_assert(std::is_invocable_v); + using U = std::invoke_result_t; + static_assert(detail::IsMaybe::value); + if (isSome()) { + return std::invoke(std::forward(aFunc), extract()); + } + return std::remove_cv_t>{}; + } + /* If |isSome()|, empties this Maybe and destroys its contents. */ constexpr void reset() { if (isSome()) { @@ -753,6 +814,17 @@ class Maybe { return val; } + template + constexpr auto andThen(Func&& aFunc) const { + static_assert(std::is_invocable_v); + using U = std::invoke_result_t; + static_assert(detail::IsMaybe::value); + if (isSome()) { + return std::invoke(std::forward(aFunc), ref()); + } + return std::remove_cv_t>{}; + } + bool refEquals(const Maybe& aOther) const { return mValue == aOther.mValue; } diff --git a/mfbt/tests/TestMaybe.cpp b/mfbt/tests/TestMaybe.cpp index 3ed81e9e3f20..a38ff9ad5ef8 100644 --- a/mfbt/tests/TestMaybe.cpp +++ b/mfbt/tests/TestMaybe.cpp @@ -1457,6 +1457,110 @@ static bool TestReference() { return true; } +static Maybe IncrementAndReturnTag(BasicValue& aValue) { + gFunctionWasApplied = true; + aValue.SetTag(aValue.GetTag() + 1); + return Some(aValue.GetTag()); +} + +static Maybe AccessValueAndReturnNothing(const BasicValue&) { + gFunctionWasApplied = true; + return Nothing(); +} + +static Maybe AccessValueAndReturnOther(const BasicValue&) { + gFunctionWasApplied = true; + return Some(42); +} + +struct IncrementAndReturnTagFunctor { + IncrementAndReturnTagFunctor() : mBy(1) {} + + Maybe operator()(BasicValue& aValue) { + aValue.SetTag(aValue.GetTag() + mBy.GetTag()); + return Some(aValue.GetTag()); + } + + BasicValue mBy; +}; + +struct AccessValueAndReturnOtherFunctor { + explicit AccessValueAndReturnOtherFunctor(int aVal) : mBy(aVal) {} + + Maybe operator()() { + gFunctionWasApplied = true; + return Some(mBy); + } + + BasicValue mBy; +}; + +static bool TestAndThen() { + // Check that andThen handles the 'Nothing' case. + gFunctionWasApplied = false; + Maybe mayValue; + Maybe 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& 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 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; }