From e65fe1b93684f9517599be695eb40aa4537fc6c7 Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Sun, 5 Jan 2025 18:53:16 +0100 Subject: Add status_or --- .clang-tidy | 1 + asl/BUILD.bazel | 2 + asl/option.hpp | 1 - asl/status.hpp | 5 +- asl/status_or.hpp | 170 ++++++++++++++++++++++++++++++++++++++++++ asl/tests/status_or_tests.cpp | 88 ++++++++++++++++++++++ todo.txt | 1 - 7 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 asl/status_or.hpp create mode 100644 asl/tests/status_or_tests.cpp diff --git a/.clang-tidy b/.clang-tidy index 9da33e4..d7337bd 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -31,3 +31,4 @@ Checks: - "-readability-use-anyofallof" - "-readability-function-cognitive-complexity" - "-readability-math-missing-parentheses" + - "-*-rvalue-reference-param-not-moved" diff --git a/asl/BUILD.bazel b/asl/BUILD.bazel index da1018e..571b8f9 100644 --- a/asl/BUILD.bazel +++ b/asl/BUILD.bazel @@ -21,6 +21,7 @@ cc_library( "print.hpp", "span.hpp", "status.hpp", + "status_or.hpp", "string.hpp", "string_view.hpp", "utility.hpp", @@ -61,6 +62,7 @@ cc_library( "option", "span", "status", + "status_or", "string", "string_view", "utility", diff --git a/asl/option.hpp b/asl/option.hpp index 8c0a573..eb6a91c 100644 --- a/asl/option.hpp +++ b/asl/option.hpp @@ -35,7 +35,6 @@ concept not_assignable_from_option = !assignable_from&&> && !assignable_from&&>; - template concept not_constructible_assignable_from_option = not_constructible_from_option && diff --git a/asl/status.hpp b/asl/status.hpp index 0d1175e..aa64e4e 100644 --- a/asl/status.hpp +++ b/asl/status.hpp @@ -75,7 +75,7 @@ public: } constexpr status(status&& other) - : m_payload{exchange(other.m_payload, nullptr)} + : m_payload{exchange(other.m_payload, status_to_payload(other.code()))} {} constexpr status& operator=(const status& other) @@ -93,7 +93,8 @@ public: { if (&other != this) { - swap(m_payload, other.m_payload); + if (!is_inline()) { unref(); } + m_payload = exchange(other.m_payload, status_to_payload(other.code())); } return *this; } diff --git a/asl/status_or.hpp b/asl/status_or.hpp new file mode 100644 index 0000000..04fcc4a --- /dev/null +++ b/asl/status_or.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include "asl/status.hpp" +#include "asl/maybe_uninit.hpp" + +namespace asl +{ + +template +class status_or +{ + status m_status; + maybe_uninit m_value{}; + +public: + // @Todo Convert copy + // @Todo Convert move + + constexpr status_or(const status_or& other) + requires copy_constructible + : m_status{other.m_status} + { + if (other.ok()) + { + m_value.construct_unsafe(other.m_value.as_init_unsafe()); + } + } + + constexpr status_or(status_or&& other) + requires move_constructible + : m_status{ASL_MOVE(other.m_status)} + { + if (other.ok()) + { + m_value.construct_unsafe(ASL_MOVE(other.m_value.as_init_unsafe())); + } + } + + constexpr status_or& operator=(const status_or& other) + requires copyable + { + if (&other != this) + { + if (ok()) + { + if (other.ok()) + { + m_value.assign_unsafe(other.m_value.as_init_unsafe()); + } + else + { + m_value.destroy_unsafe(); + } + } + else if (other.ok()) + { + m_value.construct_unsafe(other.m_value.as_init_unsafe()); + } + m_status = other.m_status; + } + return *this; + } + + constexpr status_or& operator=(status_or&& other) + requires moveable + { + if (&other != this) + { + if (ok()) + { + if (other.ok()) + { + m_value.assign_unsafe(ASL_MOVE(other.m_value.as_init_unsafe())); + } + else + { + m_value.destroy_unsafe(); + } + } + else if (other.ok()) + { + m_value.construct_unsafe(ASL_MOVE(other.m_value.as_init_unsafe())); + } + m_status = ASL_MOVE(other.m_status); + } + return *this; + } + + constexpr ~status_or() + { + if (m_status.ok()) + { + m_value.destroy_unsafe(); + } + } + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr status_or(const status& status) : m_status{status} + { + ASL_ASSERT_RELEASE(!m_status.ok()); + } + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr status_or(status&& status) : m_status{ASL_MOVE(status)} + { + ASL_ASSERT_RELEASE(!m_status.ok()); + } + + status_or& operator=(status status) = delete; + + template + constexpr explicit (!convertible_from) + status_or(U&& value) + requires ( + constructible_from && + !same_as, status_or> && + !same_as, status> + ) + : m_status{status_code::ok} + , m_value{in_place, ASL_FWD(value)} + {} + + constexpr bool ok() const { return m_status.ok(); } + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr operator bool() const { return m_status; } + + constexpr status_code code() const { return m_status.code(); } + + constexpr string_view message() const { return m_status.message(); } + + // @Todo(C++23) Deducing this + constexpr const T& value() const& + { + ASL_ASSERT_RELEASE(ok()); + return m_value.as_init_unsafe(); + } + + constexpr T& value() & + { + ASL_ASSERT_RELEASE(ok()); + return m_value.as_init_unsafe(); + } + + constexpr T&& value() && + { + ASL_ASSERT_RELEASE(ok()); + return ASL_MOVE(m_value.as_init_unsafe()); + } + + template + constexpr T value_or(U&& other_value) const& + requires copy_constructible && convertible_from + { + return ok() ? value() : static_cast(ASL_FWD(other_value)); + } + + template + constexpr T value_or(U&& other_value) && + requires move_constructible && convertible_from + { + return ok() ? ASL_MOVE(value()) : static_cast(ASL_FWD(other_value)); + } +}; + +template +status_or(T) -> status_or; + +} // namespace asl + diff --git a/asl/tests/status_or_tests.cpp b/asl/tests/status_or_tests.cpp new file mode 100644 index 0000000..5038e7e --- /dev/null +++ b/asl/tests/status_or_tests.cpp @@ -0,0 +1,88 @@ +#include "asl/status_or.hpp" +#include "asl/testing/testing.hpp" +#include "asl/tests/test_types.hpp" + +static_assert(asl::copyable>); +static_assert(asl::moveable>); + +static_assert(asl::copyable>); +static_assert(asl::moveable>); + +static_assert(!asl::copyable>); +static_assert(asl::moveable>); + +static_assert(!asl::copyable>); +static_assert(!asl::moveable>); + +ASL_TEST(ok) +{ + asl::status_or s = 6; + ASL_TEST_EXPECT(s.ok()); + ASL_TEST_EXPECT(s); + ASL_TEST_EXPECT(s.code() == asl::status_code::ok); +} + +ASL_TEST(from_status) +{ + asl::status_or s = asl::internal_error(); + ASL_TEST_EXPECT(!s.ok()); + ASL_TEST_EXPECT(!s); + ASL_TEST_EXPECT(s.code() == asl::status_code::internal); + ASL_TEST_EXPECT(s.message() == ""_sv); + + asl::status_or s2 = asl::internal_error("oh no"); + ASL_TEST_EXPECT(!s2.ok()); + ASL_TEST_EXPECT(!s2); + ASL_TEST_EXPECT(s2.code() == asl::status_code::internal); + ASL_TEST_EXPECT(s2.message() == "oh no"_sv); + + asl::status_or s3 = asl::internal_error("{} {}", 1, 2); + ASL_TEST_EXPECT(!s3.ok()); + ASL_TEST_EXPECT(!s3); + ASL_TEST_EXPECT(s3.code() == asl::status_code::internal); + ASL_TEST_EXPECT(s3.message() == "1 2"_sv); +} + +ASL_TEST(destructor) +{ + bool d = false; + + { + asl::status_or s{&d}; + ASL_TEST_EXPECT(s.ok()); + ASL_TEST_EXPECT(!d); + + asl::status_or s2 = ASL_MOVE(s); + ASL_TEST_EXPECT(s.ok()); + ASL_TEST_EXPECT(!d); + + s = ASL_MOVE(s2); + ASL_TEST_EXPECT(s.ok()); + ASL_TEST_EXPECT(!d); + } + + ASL_TEST_EXPECT(d); +} + +ASL_TEST(copy) +{ + asl::status_or s = 7; + asl::status_or s2 = s; + s = s2; + + ASL_TEST_EXPECT(s.ok()); + ASL_TEST_EXPECT(s2.ok()); + + ASL_TEST_EXPECT(s.value() == 7); + ASL_TEST_EXPECT(s2.value() == 7); +} + +ASL_TEST(value_or) +{ + asl::status_or s = 7; + asl::status_or s2 = asl::internal_error(); + + ASL_TEST_EXPECT(s.value_or(45) == 7); + ASL_TEST_EXPECT(s2.value_or(45) == 45); +} + diff --git a/todo.txt b/todo.txt index 2cc483d..52c85cf 100644 --- a/todo.txt +++ b/todo.txt @@ -2,5 +2,4 @@ hashing hash_set hash_map logging -status_or dynlib -- cgit