From 7d89fb164becb44d014023bc03688cef00e179ad Mon Sep 17 00:00:00 2001 From: Jiyong Park Date: Wed, 15 May 2019 19:17:48 +0900 Subject: [PATCH] Add android::base::expected android::base::expected is an Android implementation of the std::expected proposal. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0323r7.html For usage, refer to the expected.h header file and expected_test.cpp Bug: 132145659 Test: libbase_test Change-Id: I65d3a1ecf8654d9858989755dfd0065c81f7b209 --- base/Android.bp | 1 + base/expected_test.cpp | 745 +++++++++++++++++++++++++++ base/include/android-base/expected.h | 600 +++++++++++++++++++++ 3 files changed, 1346 insertions(+) create mode 100644 base/expected_test.cpp create mode 100644 base/include/android-base/expected.h diff --git a/base/Android.bp b/base/Android.bp index 340f81435..58d3a5043 100644 --- a/base/Android.bp +++ b/base/Android.bp @@ -131,6 +131,7 @@ cc_test { "cmsg_test.cpp", "endian_test.cpp", "errors_test.cpp", + "expected_test.cpp", "file_test.cpp", "logging_test.cpp", "macros_test.cpp", diff --git a/base/expected_test.cpp b/base/expected_test.cpp new file mode 100644 index 000000000..490ced456 --- /dev/null +++ b/base/expected_test.cpp @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2019 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 "android-base/expected.h" + +#include +#include +#include + +#include + +using android::base::expected; +using android::base::unexpected; + +typedef expected exp_int; +typedef expected exp_double; +typedef expected exp_string; +typedef expected, int> exp_pair; + +struct T { + int a; + int b; + T() = default; + T(int a, int b) noexcept : a(a), b(b) {} +}; +bool operator==(const T& x, const T& y) { + return x.a == y.a && x.b == y.b; +} +bool operator!=(const T& x, const T& y) { + return x.a != y.a || x.b != y.b; +} + +struct E { + std::string message; + int cause; + E(const std::string& message, int cause) : message(message), cause(cause) {} +}; + +typedef expected exp_complex; + +TEST(Expected, testDefaultConstructible) { + exp_int e; + EXPECT_TRUE(e.has_value()); + EXPECT_EQ(0, e.value()); + + exp_complex e2; + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(T(0,0), e2.value()); +} + +TEST(Expected, testCopyConstructible) { + exp_int e; + exp_int e2 = e; + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(0, e.value()); + EXPECT_EQ(0, e2.value()); +} + +TEST(Expected, testMoveConstructible) { + exp_int e; + exp_int e2 = std::move(e); + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(0, e.value()); + EXPECT_EQ(0, e2.value()); + + exp_string e3(std::string("hello")); + exp_string e4 = std::move(e3); + + EXPECT_TRUE(e3.has_value()); + EXPECT_TRUE(e4.has_value()); + EXPECT_EQ("", e3.value()); // e3 is moved + EXPECT_EQ("hello", e4.value()); +} + +TEST(Expected, testCopyConstructibleFromConvertibleType) { + exp_double e = 3.3f; + exp_int e2 = e; + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(3.3f, e.value()); + EXPECT_EQ(3, e2.value()); +} + +TEST(Expected, testMoveConstructibleFromConvertibleType) { + exp_double e = 3.3f; + exp_int e2 = std::move(e); + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(3.3f, e.value()); + EXPECT_EQ(3, e2.value()); +} + +TEST(Expected, testConstructibleFromValue) { + exp_int e = 3; + exp_double e2 = 5.5f; + exp_string e3 = std::string("hello"); + exp_complex e4 = T(10, 20); + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_TRUE(e3.has_value()); + EXPECT_TRUE(e4.has_value()); + EXPECT_EQ(3, e.value()); + EXPECT_EQ(5.5f, e2.value()); + EXPECT_EQ("hello", e3.value()); + EXPECT_EQ(T(10,20), e4.value()); +} + +TEST(Expected, testConstructibleFromMovedValue) { + std::string hello = "hello"; + exp_string e = std::move(hello); + + EXPECT_TRUE(e.has_value()); + EXPECT_EQ("hello", e.value()); + EXPECT_EQ("", hello); +} + +TEST(Expected, testConstructibleFromConvertibleValue) { + exp_int e = 3.3f; // double to int + exp_string e2 = "hello"; // char* to std::string + EXPECT_TRUE(e.has_value()); + EXPECT_EQ(3, e.value()); + + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ("hello", e2.value()); +} + +TEST(Expected, testConstructibleFromUnexpected) { + exp_int::unexpected_type unexp = unexpected(10); + exp_int e = unexp; + + exp_double::unexpected_type unexp2 = unexpected(10.5f); + exp_double e2 = unexp2; + + exp_string::unexpected_type unexp3 = unexpected(std::string("error")); + exp_string e3 = unexp3; + + EXPECT_FALSE(e.has_value()); + EXPECT_FALSE(e2.has_value()); + EXPECT_FALSE(e3.has_value()); + EXPECT_EQ(10, e.error()); + EXPECT_EQ(10.5f, e2.error()); + EXPECT_EQ("error", e3.error()); +} + +TEST(Expected, testMoveConstructibleFromUnexpected) { + exp_int e = unexpected(10); + exp_double e2 = unexpected(10.5f); + exp_string e3 = unexpected(std::string("error")); + + EXPECT_FALSE(e.has_value()); + EXPECT_FALSE(e2.has_value()); + EXPECT_FALSE(e3.has_value()); + EXPECT_EQ(10, e.error()); + EXPECT_EQ(10.5f, e2.error()); + EXPECT_EQ("error", e3.error()); +} + +TEST(Expected, testConstructibleByForwarding) { + exp_string e(std::in_place, 5, 'a'); + EXPECT_TRUE(e.has_value()); + EXPECT_EQ("aaaaa", e.value()); + + exp_string e2({'a', 'b', 'c'}); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ("abc", e2.value()); + + exp_pair e3({"hello", 30}); + EXPECT_TRUE(e3.has_value()); + EXPECT_EQ("hello",e3->first); + EXPECT_EQ(30,e3->second); +} + +TEST(Expected, testDestructible) { + bool destroyed = false; + struct T { + bool* flag_; + T(bool* flag) : flag_(flag) {} + ~T() { *flag_ = true; } + }; + { + expected exp = T(&destroyed); + } + EXPECT_TRUE(destroyed); +} + +TEST(Expected, testAssignable) { + exp_int e = 10; + exp_int e2 = 20; + e = e2; + + EXPECT_EQ(20, e.value()); + EXPECT_EQ(20, e2.value()); + + exp_int e3 = 10; + exp_int e4 = 20; + e3 = std::move(e4); + + EXPECT_EQ(20, e3.value()); + EXPECT_EQ(20, e4.value()); +} + +TEST(Expected, testAssignableFromValue) { + exp_int e = 10; + e = 20; + EXPECT_EQ(20, e.value()); + + exp_double e2 = 3.5f; + e2 = 10.5f; + EXPECT_EQ(10.5f, e2.value()); + + exp_string e3 = "hello"; + e3 = "world"; + EXPECT_EQ("world", e3.value()); +} + +TEST(Expected, testAssignableFromUnexpected) { + exp_int e = 10; + e = unexpected(30); + EXPECT_FALSE(e.has_value()); + EXPECT_EQ(30, e.error()); + + exp_double e2 = 3.5f; + e2 = unexpected(10.5f); + EXPECT_FALSE(e2.has_value()); + EXPECT_EQ(10.5f, e2.error()); + + exp_string e3 = "hello"; + e3 = unexpected("world"); + EXPECT_FALSE(e3.has_value()); + EXPECT_EQ("world", e3.error()); +} + +TEST(Expected, testAssignableFromMovedValue) { + std::string world = "world"; + exp_string e = "hello"; + e = std::move(world); + + EXPECT_TRUE(e.has_value()); + EXPECT_EQ("world", e.value()); + EXPECT_EQ("", world); +} + +TEST(Expected, testAssignableFromMovedUnexpected) { + std::string world = "world"; + exp_string e = "hello"; + e = unexpected(std::move(world)); + + EXPECT_FALSE(e.has_value()); + EXPECT_EQ("world", e.error()); + EXPECT_EQ("", world); +} + +TEST(Expected, testEmplace) { + struct T { + int a; + double b; + T() {} + T(int a, double b) noexcept : a(a), b(b) {} + }; + expected exp; + T& t = exp.emplace(3, 10.5f); + + EXPECT_TRUE(exp.has_value()); + EXPECT_EQ(3, t.a); + EXPECT_EQ(10.5f, t.b); + EXPECT_EQ(3, exp.value().a); + EXPECT_EQ(10.5, exp.value().b); +} + +TEST(Expected, testSwapExpectedExpected) { + exp_int e = 10; + exp_int e2 = 20; + e.swap(e2); + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(20, e.value()); + EXPECT_EQ(10, e2.value()); +} + +TEST(Expected, testSwapUnexpectedUnexpected) { + exp_int e = unexpected(10); + exp_int e2 = unexpected(20); + e.swap(e2); + EXPECT_FALSE(e.has_value()); + EXPECT_FALSE(e2.has_value()); + EXPECT_EQ(20, e.error()); + EXPECT_EQ(10, e2.error()); +} + +TEST(Expected, testSwapExpectedUnepected) { + exp_int e = 10; + exp_int e2 = unexpected(30); + e.swap(e2); + EXPECT_FALSE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(30, e.error()); + EXPECT_EQ(10, e2.value()); +} + +TEST(Expected, testDereference) { + struct T { + int a; + double b; + T() {} + T(int a, double b) : a(a), b(b) {} + }; + expected exp = T(3, 10.5f); + + EXPECT_EQ(3, exp->a); + EXPECT_EQ(10.5f, exp->b); + + EXPECT_EQ(3, (*exp).a); + EXPECT_EQ(10.5f, (*exp).b); +} + +TEST(Expected, testTest) { + exp_int e = 10; + EXPECT_TRUE(e); + EXPECT_TRUE(e.has_value()); + + exp_int e2 = unexpected(10); + EXPECT_FALSE(e2); + EXPECT_FALSE(e2.has_value()); +} + +TEST(Expected, testGetValue) { + exp_int e = 10; + EXPECT_EQ(10, e.value()); + EXPECT_EQ(10, e.value_or(20)); + + exp_int e2 = unexpected(10); + EXPECT_EQ(10, e2.error()); + EXPECT_EQ(20, e2.value_or(20)); +} + +TEST(Expected, testSameValues) { + exp_int e = 10; + exp_int e2 = 10; + EXPECT_TRUE(e == e2); + EXPECT_TRUE(e2 == e); + EXPECT_FALSE(e != e2); + EXPECT_FALSE(e2 != e); +} + +TEST(Expected, testDifferentValues) { + exp_int e = 10; + exp_int e2 = 20; + EXPECT_FALSE(e == e2); + EXPECT_FALSE(e2 == e); + EXPECT_TRUE(e != e2); + EXPECT_TRUE(e2 != e); +} + +TEST(Expected, testValueWithError) { + exp_int e = 10; + exp_int e2 = unexpected(10); + EXPECT_FALSE(e == e2); + EXPECT_FALSE(e2 == e); + EXPECT_TRUE(e != e2); + EXPECT_TRUE(e2 != e); +} + +TEST(Expected, testSameErrors) { + exp_int e = unexpected(10); + exp_int e2 = unexpected(10); + EXPECT_TRUE(e == e2); + EXPECT_TRUE(e2 == e); + EXPECT_FALSE(e != e2); + EXPECT_FALSE(e2 != e); +} + +TEST(Expected, testDifferentErrors) { + exp_int e = unexpected(10); + exp_int e2 = unexpected(20); + EXPECT_FALSE(e == e2); + EXPECT_FALSE(e2 == e); + EXPECT_TRUE(e != e2); + EXPECT_TRUE(e2 != e); +} + +TEST(Expected, testCompareWithSameValue) { + exp_int e = 10; + int value = 10; + EXPECT_TRUE(e == value); + EXPECT_TRUE(value == e); + EXPECT_FALSE(e != value); + EXPECT_FALSE(value != e); +} + +TEST(Expected, testCompareWithDifferentValue) { + exp_int e = 10; + int value = 20; + EXPECT_FALSE(e == value); + EXPECT_FALSE(value == e); + EXPECT_TRUE(e != value); + EXPECT_TRUE(value != e); +} + +TEST(Expected, testCompareWithSameError) { + exp_int e = unexpected(10); + exp_int::unexpected_type error = 10; + EXPECT_TRUE(e == error); + EXPECT_TRUE(error == e); + EXPECT_FALSE(e != error); + EXPECT_FALSE(error != e); +} + +TEST(Expected, testCompareWithDifferentError) { + exp_int e = unexpected(10); + exp_int::unexpected_type error = 20; + EXPECT_FALSE(e == error); + EXPECT_FALSE(error == e); + EXPECT_TRUE(e != error); + EXPECT_TRUE(error != e); +} + +TEST(Expected, testDivideExample) { + struct QR { + int quotient; + int remainder; + QR(int q, int r) noexcept : quotient(q), remainder(r) {} + bool operator==(const QR& rhs) const { + return quotient == rhs.quotient && remainder == rhs.remainder; + } + bool operator!=(const QR& rhs) const { + return quotient != rhs.quotient || remainder == rhs.remainder; + } + }; + + auto divide = [](int x, int y) -> expected { + if (y == 0) { + return unexpected(E("divide by zero", -1)); + } else { + return QR(x / y, x % y); + } + }; + + EXPECT_FALSE(divide(10, 0)); + EXPECT_EQ("divide by zero", divide(10, 0).error().message); + EXPECT_EQ(-1, divide(10, 0).error().cause); + + EXPECT_TRUE(divide(10, 3)); + EXPECT_EQ(QR(3, 1), divide(10, 3)); +} + +TEST(Expected, testPair) { + auto test = [](bool yes) -> exp_pair { + if (yes) { + return exp_pair({"yes", 42}); + } else { + return unexpected(42); + } + }; + + auto r = test(true); + EXPECT_TRUE(r); + EXPECT_EQ("yes", r->first); +} + +// copied from result_test.cpp +struct ConstructorTracker { + static size_t constructor_called; + static size_t copy_constructor_called; + static size_t move_constructor_called; + static size_t copy_assignment_called; + static size_t move_assignment_called; + + template >* = nullptr> + ConstructorTracker(T&& string) : string(string) { + ++constructor_called; + } + ConstructorTracker(const ConstructorTracker& ct) { + ++copy_constructor_called; + string = ct.string; + } + ConstructorTracker(ConstructorTracker&& ct) noexcept { + ++move_constructor_called; + string = std::move(ct.string); + } + ConstructorTracker& operator=(const ConstructorTracker& ct) { + ++copy_assignment_called; + string = ct.string; + return *this; + } + ConstructorTracker& operator=(ConstructorTracker&& ct) noexcept { + ++move_assignment_called; + string = std::move(ct.string); + return *this; + } + static void Reset() { + constructor_called = 0; + copy_constructor_called = 0; + move_constructor_called = 0; + copy_assignment_called = 0; + move_assignment_called = 0; + } + std::string string; +}; + +size_t ConstructorTracker::constructor_called = 0; +size_t ConstructorTracker::copy_constructor_called = 0; +size_t ConstructorTracker::move_constructor_called = 0; +size_t ConstructorTracker::copy_assignment_called = 0; +size_t ConstructorTracker::move_assignment_called = 0; + +typedef expected exp_track; + +TEST(Expected, testNumberOfCopies) { + // default constructor + ConstructorTracker::Reset(); + exp_track e("hello"); + EXPECT_EQ(1U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + // copy constructor + ConstructorTracker::Reset(); + exp_track e2 = e; + EXPECT_EQ(0U, ConstructorTracker::constructor_called); + EXPECT_EQ(1U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + // move constructor + ConstructorTracker::Reset(); + exp_track e3 = std::move(e); + EXPECT_EQ(0U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(1U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + // construct from lvalue + ConstructorTracker::Reset(); + ConstructorTracker ct = "hello"; + exp_track e4(ct); + EXPECT_EQ(1U, ConstructorTracker::constructor_called); + EXPECT_EQ(1U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + // construct from rvalue + ConstructorTracker::Reset(); + ConstructorTracker ct2 = "hello"; + exp_track e5(std::move(ct2)); + EXPECT_EQ(1U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(1U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + // copy assignment + ConstructorTracker::Reset(); + exp_track e6 = "hello"; + exp_track e7 = "world"; + e7 = e6; + EXPECT_EQ(2U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(1U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + // move assignment + ConstructorTracker::Reset(); + exp_track e8 = "hello"; + exp_track e9 = "world"; + e9 = std::move(e8); + EXPECT_EQ(2U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(1U, ConstructorTracker::move_assignment_called); + + // swap + ConstructorTracker::Reset(); + exp_track e10 = "hello"; + exp_track e11 = "world"; + std::swap(e10, e11); + EXPECT_EQ(2U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(1U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(2U, ConstructorTracker::move_assignment_called); +} + +TEST(Expected, testNoCopyOnReturn) { + auto test = [](const std::string& in) -> exp_track { + if (in.empty()) { + return "literal string"; + } + if (in == "test2") { + return ConstructorTracker(in + in + "2"); + } + ConstructorTracker result(in + " " + in); + return result; + }; + + ConstructorTracker::Reset(); + auto result1 = test(""); + ASSERT_TRUE(result1); + EXPECT_EQ("literal string", result1->string); + EXPECT_EQ(1U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + ConstructorTracker::Reset(); + auto result2 = test("test2"); + ASSERT_TRUE(result2); + EXPECT_EQ("test2test22", result2->string); + EXPECT_EQ(1U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(1U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); + + ConstructorTracker::Reset(); + auto result3 = test("test3"); + ASSERT_TRUE(result3); + EXPECT_EQ("test3 test3", result3->string); + EXPECT_EQ(1U, ConstructorTracker::constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_constructor_called); + EXPECT_EQ(1U, ConstructorTracker::move_constructor_called); + EXPECT_EQ(0U, ConstructorTracker::copy_assignment_called); + EXPECT_EQ(0U, ConstructorTracker::move_assignment_called); +} + +TEST(Expected, testNested) { + expected e = "hello"; + + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e.value().has_value()); + EXPECT_TRUE(e); + EXPECT_TRUE(*e); + EXPECT_EQ("hello", e.value().value()); + + expected e2 = unexpected("world"); + EXPECT_FALSE(e2.has_value()); + EXPECT_FALSE(e2); + EXPECT_EQ("world", e2.error()); + + expected e3 = exp_string(unexpected("world")); + EXPECT_TRUE(e3.has_value()); + EXPECT_FALSE(e3.value().has_value()); + EXPECT_TRUE(e3); + EXPECT_FALSE(*e3); + EXPECT_EQ("world", e3.value().error()); +} + +constexpr bool equals(const char* a, const char* b) { + return (a == nullptr && b == nullptr) || + (a != nullptr && b != nullptr && *a == *b && + (*a == '\0' || equals(a + 1, b + 1))); +} + +TEST(Expected, testConstexpr) { + // Compliation error will occur if these expressions can't be + // evaluated at compile time + constexpr exp_int e(3); + constexpr exp_int::unexpected_type err(3); + constexpr int i = 4; + + // default constructor + static_assert(exp_int().value() == 0); + // copy constructor + static_assert(exp_int(e).value() == 3); + // move constructor + static_assert(exp_int(exp_int(4)).value() == 4); + // copy construct from value + static_assert(exp_int(i).value() == 4); + // copy construct from unexpected + static_assert(exp_int(err).error() == 3); + // move costruct from unexpected + static_assert(exp_int(unexpected(3)).error() == 3); + // observers + static_assert(*exp_int(3) == 3); + static_assert(exp_int(3).has_value() == true); + static_assert(exp_int(3).value_or(4) == 3); + + typedef expected exp_s; + constexpr exp_s s("hello"); + constexpr const char* c = "hello"; + static_assert(equals(exp_s().value(), nullptr)); + static_assert(equals(exp_s(s).value(), "hello")); + static_assert(equals(exp_s(exp_s("hello")).value(), "hello")); + static_assert(equals(exp_s("hello").value(), "hello")); + static_assert(equals(exp_s(c).value(), "hello")); +} + +TEST(Expected, testWithNonConstructible) { + struct AssertNotConstructed { + AssertNotConstructed() = delete; + }; + + expected v(42); + EXPECT_TRUE(v.has_value()); + EXPECT_EQ(42, v.value()); + + expected e(unexpected(42)); + EXPECT_FALSE(e.has_value()); + EXPECT_EQ(42, e.error()); +} + +TEST(Expected, testWithMoveOnlyType) { + typedef expected,std::unique_ptr> exp_ptr; + exp_ptr e(std::make_unique(3)); + exp_ptr e2(unexpected(std::make_unique(4))); + + EXPECT_TRUE(e.has_value()); + EXPECT_FALSE(e2.has_value()); + EXPECT_EQ(3, *(e.value())); + EXPECT_EQ(4, *(e2.error())); + + e2 = std::move(e); + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(e2.has_value()); + EXPECT_EQ(3, *(e2.value())); +} diff --git a/base/include/android-base/expected.h b/base/include/android-base/expected.h new file mode 100644 index 000000000..d70e50a3a --- /dev/null +++ b/base/include/android-base/expected.h @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2019 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 +#include +#include +#include +#include + +// android::base::expected is an Android implementation of the std::expected +// proposal. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0323r7.html +// +// Usage: +// using android::base::expected; +// using android::base::unexpected; +// +// expected safe_divide(double i, double j) { +// if (j == 0) return unexpected("divide by zero"); +// else return i / j; +// } +// +// void test() { +// auto q = safe_divide(10, 0); +// if (q) { printf("%f\n", q.value()); } +// else { printf("%s\n", q.error().c_str()); } +// } +// +// When the proposal becomes part of the standard and is implemented by +// libcxx, this will be removed and android::base::expected will be +// type alias to std::expected. +// + +namespace android { +namespace base { + +// Synopsis +template + class expected; + +template + class unexpected; +template + unexpected(E) -> unexpected; + +template + class bad_expected_access; + +template<> + class bad_expected_access; + +struct unexpect_t { + explicit unexpect_t() = default; +}; +inline constexpr unexpect_t unexpect{}; + +// macros for SFINAE +#define _ENABLE_IF(...) \ + , std::enable_if_t<(__VA_ARGS__)>* = nullptr + +// Define NODISCARD_EXPECTED to prevent expected from being +// ignored when used as a return value. This is off by default. +#ifdef NODISCARD_EXPECTED +#define _NODISCARD_ [[nodiscard]] +#else +#define _NODISCARD_ +#endif + +// Class expected +template +class _NODISCARD_ expected { + public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + + template + using rebind = expected; + + // constructors + constexpr expected() = default; + constexpr expected(const expected& rhs) = default; + constexpr expected(expected&& rhs) noexcept = default; + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !(!std::is_convertible_v || + !std::is_convertible_v) /* non-explicit */ + )> + constexpr expected(const expected& rhs) { + if (rhs.has_value()) var_ = rhs.value(); + else var_ = unexpected(rhs.error()); + } + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + (!std::is_convertible_v || + !std::is_convertible_v) /* explicit */ + )> + constexpr explicit expected(const expected& rhs) { + if (rhs.has_value()) var_ = rhs.value(); + else var_ = unexpected(rhs.error()); + } + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !(!std::is_convertible_v || + !std::is_convertible_v) /* non-explicit */ + )> + constexpr expected(expected&& rhs) { + if (rhs.has_value()) var_ = std::move(rhs.value()); + else var_ = unexpected(std::move(rhs.error())); + } + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + (!std::is_convertible_v || + !std::is_convertible_v) /* explicit */ + )> + constexpr explicit expected(expected&& rhs) { + if (rhs.has_value()) var_ = std::move(rhs.value()); + else var_ = unexpected(std::move(rhs.error())); + } + + template && + std::is_convertible_v /* non-explicit */ + )> + constexpr expected(U&& v) + : var_(std::in_place_index<0>, std::forward(v)) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(U&& v) + : var_(std::in_place_index<0>, T(std::forward(v))) {} + + template && + std::is_convertible_v /* non-explicit */ + )> + constexpr expected(const unexpected& e) + : var_(std::in_place_index<1>, e.value()) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(const unexpected& e) + : var_(std::in_place_index<1>, E(e.value())) {} + + template && + std::is_convertible_v /* non-explicit */ + )> + constexpr expected(unexpected&& e) + : var_(std::in_place_index<1>, std::move(e.value())) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(unexpected&& e) + : var_(std::in_place_index<1>, E(std::move(e.value()))) {} + + template + )> + constexpr explicit expected(std::in_place_t, Args&&... args) + : var_(std::in_place_index<0>, std::forward(args)...) {} + + template&, Args...> + )> + constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args) + : var_(std::in_place_index<0>, il, std::forward(args)...) {} + + template + )> + constexpr explicit expected(unexpect_t, Args&&... args) + : var_(unexpected_type(std::forward(args)...)) {} + + template&, Args...> + )> + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args) + : var_(unexpected_type(il, std::forward(args)...)) {} + + // destructor + ~expected() = default; + + // assignment +// TODO(b/132145659) enable assignment operator only when the condition +// satisfies. SFNAIE doesn't work here because assignment operator should be +// non-template. We could workaround this by defining a templated parent class +// having the assignment operator. This incomplete implementation however +// doesn't allow us to copy assign expected even when T is non-copy +// assignable. The copy assignment will fail by the underlying std::variant +// anyway though the error message won't be clear. +// std::enable_if_t<( +// std::is_copy_assignable_v && +// std::is_copy_constructible_v && +// std::is_copy_assignable_v && +// std::is_copy_constructible_v && +// (std::is_nothrow_move_constructible_v || +// std::is_nothrow_move_constructible_v) +// ), expected&> + expected& operator=(const expected& rhs) { + var_ = rhs.var_; + return *this; + } +// std::enable_if_t<( +// std::is_move_constructible_v && +// std::is_move_assignable_v && +// std::is_nothrow_move_constructible_v && +// std::is_nothrow_move_assignable_v +// ), expected&> + expected& operator=(expected&& rhs) noexcept { + var_ = std::move(rhs.var_); + return *this; + } + + template && + std::is_constructible_v && + std::is_assignable_v && + std::is_nothrow_move_constructible_v + )> + expected& operator=(U&& rhs) { + var_ = T(std::forward(rhs)); + return *this; + } + + template + // TODO: std::is_nothrow_copy_constructible_v && std::is_copy_assignable_v + expected& operator=(const unexpected& rhs) { + var_ = rhs; + return *this; + } + + template && + std::is_move_assignable_v + )> + expected& operator=(unexpected&& rhs) { + var_ = std::move(rhs); + return *this; + } + + // modifiers + template + )> + T& emplace(Args&&... args) { + expected(std::in_place, std::forward(args)...).swap(*this); + return value(); + } + + template&, Args...> + )> + T& emplace(std::initializer_list il, Args&&... args) { + expected(std::in_place, il, std::forward(args)...).swap(*this); + return value(); + } + + // swap + template && + std::is_swappable_v && + (std::is_move_constructible_v || + std::is_move_constructible_v))>> + void swap(expected& rhs) noexcept( + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v) { + var_.swap(rhs.var_); + } + + // observers + constexpr const T* operator->() const { return std::addressof(value()); } + constexpr T* operator->() { return std::addressof(value()); } + constexpr const T& operator*() const& { return value(); } + constexpr T& operator*() & { return value(); } + constexpr const T&& operator*() const&& { return std::move(std::get(var_)); } + constexpr T&& operator*() && { return std::move(std::get(var_)); } + + constexpr explicit operator bool() const noexcept { return has_value(); } + constexpr bool has_value() const noexcept { return var_.index() == 0; } + + constexpr const T& value() const& { return std::get(var_); } + constexpr T& value() & { return std::get(var_); } + constexpr const T&& value() const&& { return std::move(std::get(var_)); } + constexpr T&& value() && { return std::move(std::get(var_)); } + + constexpr const E& error() const& { return std::get(var_).value(); } + constexpr E& error() & { return std::get(var_).value(); } + constexpr const E&& error() const&& { return std::move(std::get(var_)).value(); } + constexpr E&& error() && { return std::move(std::get(var_)).value(); } + + template && + std::is_convertible_v + )> + constexpr T value_or(U&& v) const& { + if (has_value()) return value(); + else return static_cast(std::forward(v)); + } + + template && + std::is_convertible_v + )> + constexpr T value_or(U&& v) && { + if (has_value()) return std::move(value()); + else return static_cast(std::forward(v)); + } + + // expected equality operators + template + friend constexpr bool operator==(const expected& x, const expected& y); + template + friend constexpr bool operator!=(const expected& x, const expected& y); + + // comparison with T + template + friend constexpr bool operator==(const expected&, const T2&); + template + friend constexpr bool operator==(const T2&, const expected&); + template + friend constexpr bool operator!=(const expected&, const T2&); + template + friend constexpr bool operator!=(const T2&, const expected&); + + // Comparison with unexpected + template + friend constexpr bool operator==(const expected&, const unexpected&); + template + friend constexpr bool operator==(const unexpected&, const expected&); + template + friend constexpr bool operator!=(const expected&, const unexpected&); + template + friend constexpr bool operator!=(const unexpected&, const expected&); + + // Specialized algorithms + template + friend void swap(expected&, expected&) noexcept; + + private: + std::variant var_; +}; + +template +constexpr bool operator==(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) { + return false; + } else if (!x.has_value()) { + return x.error() == y.error(); + } else { + return *x == *y; + } +} + +template +constexpr bool operator!=(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) { + return true; + } else if (!x.has_value()) { + return x.error() != y.error(); + } else { + return *x != *y; + } +} + +// comparison with T +template +constexpr bool operator==(const expected& x, const T2& y) { + return x.has_value() && (*x == y); +} +template +constexpr bool operator==(const T2& x, const expected& y) { + return y.has_value() && (x == *y); +} +template +constexpr bool operator!=(const expected& x, const T2& y) { + return !x.has_value() || (*x != y); +} +template +constexpr bool operator!=(const T2& x, const expected& y) { + return !y.has_value() || (x != *y); +} + +// Comparison with unexpected +template +constexpr bool operator==(const expected& x, const unexpected& y) { + return !x.has_value() && (x.error() == y.value()); +} +template +constexpr bool operator==(const unexpected& x, const expected& y) { + return !y.has_value() && (x.value() == y.error()); +} +template +constexpr bool operator!=(const expected& x, const unexpected& y) { + return x.has_value() || (x.error() != y.value()); +} +template +constexpr bool operator!=(const unexpected& x, const expected& y) { + return y.has_value() || (x.value() != y.error()); +} + +template +class unexpected { + public: + // constructors + constexpr unexpected(const unexpected&) = default; + constexpr unexpected(unexpected&&) = default; + + template + )> + constexpr unexpected(Err&& e) + : val_(std::forward(e)) {} + + template&, Args...> + )> + constexpr explicit unexpected(std::in_place_t, std::initializer_list il, Args&&... args) + : val_(il, std::forward(args)...) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + std::is_convertible_v /* non-explicit */ + )> + constexpr unexpected(const unexpected& rhs) + : val_(rhs.value()) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit unexpected(const unexpected& rhs) + : val_(E(rhs.value())) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + std::is_convertible_v /* non-explicit */ + )> + constexpr unexpected(unexpected&& rhs) + : val_(std::move(rhs.value())) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit unexpected(unexpected&& rhs) + : val_(E(std::move(rhs.value()))) {} + + // assignment + constexpr unexpected& operator=(const unexpected&) = default; + constexpr unexpected& operator=(unexpected&&) = default; + template + constexpr unexpected& operator=(const unexpected& rhs) { + val_ = rhs.value(); + return *this; + } + template + constexpr unexpected& operator=(unexpected&& rhs) { + val_ = std::forward(rhs.value()); + return *this; + } + + // observer + constexpr const E& value() const& noexcept { return val_; } + constexpr E& value() & noexcept { return val_; } + constexpr const E&& value() const&& noexcept { return std::move(val_); } + constexpr E&& value() && noexcept { return std::move(val_); } + + void swap(unexpected& other) noexcept { std::swap(val_, other.val_); } + + template + friend constexpr bool + operator==(const unexpected& e1, const unexpected& e2); + template + friend constexpr bool + operator!=(const unexpected& e1, const unexpected& e2); + + template + friend void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))); + private: + E val_; +}; + +template +constexpr bool +operator==(const unexpected& e1, const unexpected& e2) { + return e1.value() == e2.value(); +} + +template +constexpr bool +operator!=(const unexpected& e1, const unexpected& e2) { + return e1.value() != e2.value(); +} + +template +void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))) { + x.swap(y); +} + +// TODO: bad_expected_access class + +#undef _ENABLE_IF +#undef _NODISCARD_ + +} // namespace base +} // namespace android