diff options
author | 2024-01-28 15:20:47 -0500 | |
---|---|---|
committer | 2024-01-30 10:49:59 -0500 | |
commit | 9bb429aa5629a9b55943c950c7b95bffdc339b96 (patch) | |
tree | 596ddc04bf96d3c502a41142851b42ec39b927cd | |
parent | 1b685dd3b6373ba55be1c57c672a22533e16664d (diff) |
FTL: Add Expected<T, E>
Extend `base::expected` with `has_error()` and `value_opt()`.
Bug: 185536303
Test: ftl_test
Change-Id: I07d70dc8fe7ebfe2f08626dff51aef0b98430f61
-rw-r--r-- | include/ftl/expected.h | 65 | ||||
-rw-r--r-- | libs/ftl/Android.bp | 4 | ||||
-rw-r--r-- | libs/ftl/expected_test.cpp | 77 |
3 files changed, 146 insertions, 0 deletions
diff --git a/include/ftl/expected.h b/include/ftl/expected.h new file mode 100644 index 0000000000..12b6102b6f --- /dev/null +++ b/include/ftl/expected.h @@ -0,0 +1,65 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <android-base/expected.h> +#include <ftl/optional.h> + +#include <utility> + +namespace android::ftl { + +// Superset of base::expected<T, E> with monadic operations. +// +// TODO: Extend std::expected<T, E> in C++23. +// +template <typename T, typename E> +struct Expected final : base::expected<T, E> { + using Base = base::expected<T, E>; + using Base::expected; + + using Base::error; + using Base::has_value; + using Base::value; + + template <typename P> + constexpr bool has_error(P predicate) const { + return !has_value() && predicate(error()); + } + + constexpr Optional<T> value_opt() const& { + return has_value() ? Optional(value()) : std::nullopt; + } + + constexpr Optional<T> value_opt() && { + return has_value() ? Optional(std::move(value())) : std::nullopt; + } + + // Delete new for this class. Its base doesn't have a virtual destructor, and + // if it got deleted via base class pointer, it would cause undefined + // behavior. There's not a good reason to allocate this object on the heap + // anyway. + static void* operator new(size_t) = delete; + static void* operator new[](size_t) = delete; +}; + +template <typename E> +constexpr auto Unexpected(E&& error) { + return base::unexpected(std::forward<E>(error)); +} + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 918680d6a7..5ac965f566 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -10,11 +10,15 @@ package { cc_test { name: "ftl_test", test_suites: ["device-tests"], + header_libs: [ + "libbase_headers", + ], srcs: [ "algorithm_test.cpp", "cast_test.cpp", "concat_test.cpp", "enum_test.cpp", + "expected_test.cpp", "fake_guard_test.cpp", "flags_test.cpp", "function_test.cpp", diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp new file mode 100644 index 0000000000..8cb07e4696 --- /dev/null +++ b/libs/ftl/expected_test.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ftl/expected.h> +#include <gtest/gtest.h> + +#include <string> +#include <system_error> + +namespace android::test { + +using IntExp = ftl::Expected<int, std::errc>; +using StringExp = ftl::Expected<std::string, std::errc>; + +using namespace std::string_literals; + +TEST(Expected, Construct) { + // Default value. + EXPECT_TRUE(IntExp().has_value()); + EXPECT_EQ(IntExp(), IntExp(0)); + + EXPECT_TRUE(StringExp().has_value()); + EXPECT_EQ(StringExp(), StringExp("")); + + // Value. + ASSERT_TRUE(IntExp(42).has_value()); + EXPECT_EQ(42, IntExp(42).value()); + + ASSERT_TRUE(StringExp("test").has_value()); + EXPECT_EQ("test"s, StringExp("test").value()); + + // Error. + const auto exp = StringExp(ftl::Unexpected(std::errc::invalid_argument)); + ASSERT_FALSE(exp.has_value()); + EXPECT_EQ(std::errc::invalid_argument, exp.error()); +} + +TEST(Expected, HasError) { + EXPECT_FALSE(IntExp(123).has_error([](auto) { return true; })); + EXPECT_FALSE(IntExp(ftl::Unexpected(std::errc::io_error)).has_error([](auto) { return false; })); + + EXPECT_TRUE(StringExp(ftl::Unexpected(std::errc::permission_denied)).has_error([](auto e) { + return e == std::errc::permission_denied; + })); +} + +TEST(Expected, ValueOpt) { + EXPECT_EQ(ftl::Optional(-1), IntExp(-1).value_opt()); + EXPECT_EQ(std::nullopt, IntExp(ftl::Unexpected(std::errc::broken_pipe)).value_opt()); + + { + const StringExp exp("foo"s); + EXPECT_EQ(ftl::Optional('f'), + exp.value_opt().transform([](const auto& s) { return s.front(); })); + EXPECT_EQ("foo"s, exp.value()); + } + { + StringExp exp("foobar"s); + EXPECT_EQ(ftl::Optional(6), std::move(exp).value_opt().transform(&std::string::length)); + EXPECT_TRUE(exp.value().empty()); + } +} + +} // namespace android::test |