From 5633adf7c7b3ff0bf1f75c1ec820050d7ad21ca4 Mon Sep 17 00:00:00 2001 From: Ryan Olson Date: Mon, 26 Dec 2022 22:56:00 +0000 Subject: [PATCH 1/4] added immediate channel --- .../mrc/channel/v2/immediate_channel.hpp | 259 ++++++++++++++++++ cpp/mrc/tests/CMakeLists.txt | 1 + .../tests/channels/test_immediate_channel.cpp | 123 +++++++++ 3 files changed, 383 insertions(+) create mode 100644 cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp create mode 100644 cpp/mrc/tests/channels/test_immediate_channel.cpp diff --git a/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp b/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp new file mode 100644 index 000000000..0b17d0b8e --- /dev/null +++ b/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp @@ -0,0 +1,259 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * 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 "mrc/channel/status.hpp" +#include "mrc/core/expected.hpp" +#include "mrc/core/std23_expected.hpp" + +#include + +#include +#include +#include + +namespace mrc::channel::v2 { + +template +class ImmediateChannel +{ + public: + using mutex_type = std::mutex; + + // mrc: hotpath + struct WriteOperation + { + WriteOperation(ImmediateChannel& parent, T&& data) : m_parent(parent), m_data(std::move(data)) {} + + // writes always suspend + constexpr static auto await_ready() noexcept -> bool + { + return false; + } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> std::coroutine_handle<> + { + auto lock = std::unique_lock{m_parent.m_mutex}; + m_awaiting_coroutine = awaiting_coroutine; + + // if the channel was closed, resume immediate and throw an error in the await_resume method + if (m_parent.m_closed.load(std::memory_order::acquire)) [[unlikely]] + { + m_channel_closed = true; + return awaiting_coroutine; + } + + // if there are no readers to resume, we insert ourself into the lifo queue of writers with data and yield + if (m_parent.m_read_waiters == nullptr) + { + m_next = m_parent.m_write_waiters; + m_parent.m_write_waiters = this; + return std::noop_coroutine(); + } + + // otherwise we prepare the reader for resumption + auto* reader = m_parent.m_read_waiters; + m_parent.m_read_waiters = reader->m_next; + reader->m_data = std::move(m_data); + + // then we insert ourself at the end of the fifo queue of writers without data awaiting to be resumed + if (m_parent.m_write_resumers == nullptr) + { + m_parent.m_write_resumers = this; + } + else + { + // put current writer at the end of the fifo writer resumer list + auto* write_resumer = m_parent.m_write_resumers->m_next; + while (write_resumer->m_next != nullptr) + { + write_resumer = write_resumer->m_next; + } + write_resumer->m_next = this; + } + + // resume the reader via symmetric transfer + return reader->m_awaiting_coroutine; + } + + auto await_resume() -> void + { + if (m_channel_closed) [[unlikely]] + { + auto error = Error::create(ErrorCode::ChannelClosed, "write failed on closed channel"); + // LOG(ERROR) << error.value().message(); + throw error.value(); + } + } + + ImmediateChannel& m_parent; + std::coroutine_handle<> m_awaiting_coroutine; + WriteOperation* m_next{nullptr}; + bool m_channel_closed{false}; + T m_data; + std::unique_lock m_lock; + }; + + // mrc: hotpath + struct ReadOperation + { + bool await_ready() + { + m_lock = std::unique_lock(m_parent.m_mutex); + return m_parent.try_read_with_lock(this, m_lock); + } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void + { + DCHECK(m_lock.owns_lock()); + auto lock = std::move(m_lock); + + m_awaiting_coroutine = awaiting_coroutine; + m_next = m_parent.m_read_waiters; + m_parent.m_read_waiters = this; + } + + auto await_resume() noexcept -> std23::expected + { + if (m_channel_closed) [[unlikely]] + { + return std23::unexpected(Status::closed); + } + + return {std::move(m_data)}; + } + + ImmediateChannel& m_parent; + std::coroutine_handle<> m_awaiting_coroutine; + ReadOperation* m_next{nullptr}; + T m_data; + bool m_channel_closed{false}; + std::unique_lock m_lock; + }; + + [[nodiscard]] WriteOperation async_write(T&& data) + { + // mrc: hotpath + return WriteOperation{*this, std::move(data)}; + } + + [[nodiscard]] ReadOperation async_read() + { + // mrc: hotpath + return ReadOperation{*this}; + } + + void close() + { + // Only wake up waiters once. + if (m_closed.load(std::memory_order::acquire)) + { + return; + } + + std::unique_lock lock{m_mutex}; + auto first_closer = !m_closed.exchange(true, std::memory_order::release); + + // only the first caller of close should continue + if (first_closer) + { + // the readers flush the writers, then after all writers are finished, + // the readers will see the channel is closed and resume with the closed status + while (m_read_waiters != nullptr) + { + auto* to_resume = m_read_waiters; + m_read_waiters = m_read_waiters->m_next; + lock.unlock(); + to_resume->m_channel_closed = true; + to_resume->m_awaiting_coroutine.resume(); + lock.lock(); + } + } + } + + private: + // mrc: hotpath + bool try_read_with_lock(ReadOperation* read_op, std::unique_lock& lock) + { + // if there are any writers in any state, we will resume them + while (m_write_waiters != nullptr || m_write_resumers) + { + // first process any writer that still holds data + if (m_write_waiters != nullptr) + { + // pop writer off the lifo writers queue + auto resume_in_future = m_write_waiters; + m_write_waiters = m_write_waiters->m_next; + resume_in_future->m_next = nullptr; + + // transfer the data object to this ReadOperation + read_op->m_data = std::move(resume_in_future->m_data); + + // the writer we pulled off the writers queue we push to the end of waiters fifo queue + if (m_write_resumers == nullptr) + { + m_write_resumers = resume_in_future; + } + else + { + auto last = m_write_resumers; + while (last->m_next != nullptr) + { + last = last->m_next; + } + last->m_next = resume_in_future; + } + + lock.unlock(); + return true; + } + + // there were no writers with data, so there must be at least one waiting to be resumed + DCHECK(m_write_resumers != nullptr); + + // pop off the first resumer + auto* to_resume = m_write_resumers; + m_write_resumers = to_resume->m_next; + + // resume the writer + lock.unlock(); + to_resume->m_awaiting_coroutine.resume(); + lock.lock(); + } + + // if there are no readers and the channel is closed, we should resume immediately + if (m_closed.load(std::memory_order::acquire)) [[unlikely]] + { + read_op->m_channel_closed = true; + lock.unlock(); + return true; + } + + // there are no writers present and the channel is still open ==> this reader must suspend + // the await_suspend method is responsible for unlocking + return false; + } + + mutex_type m_mutex; + WriteOperation* m_write_waiters{nullptr}; + WriteOperation* m_write_resumers{nullptr}; + ReadOperation* m_read_waiters{nullptr}; + std::atomic m_closed{false}; +}; + +} // namespace mrc::channel::v2 diff --git a/cpp/mrc/tests/CMakeLists.txt b/cpp/mrc/tests/CMakeLists.txt index 366afed8e..a50a767b6 100644 --- a/cpp/mrc/tests/CMakeLists.txt +++ b/cpp/mrc/tests/CMakeLists.txt @@ -15,6 +15,7 @@ # Keep all source files sorted!!! add_executable(test_mrc + channels/test_immediate_channel.cpp coroutines/test_event.cpp coroutines/test_latch.cpp coroutines/test_ring_buffer.cpp diff --git a/cpp/mrc/tests/channels/test_immediate_channel.cpp b/cpp/mrc/tests/channels/test_immediate_channel.cpp new file mode 100644 index 000000000..3681b2e63 --- /dev/null +++ b/cpp/mrc/tests/channels/test_immediate_channel.cpp @@ -0,0 +1,123 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * 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 "mrc/channel/v2/immediate_channel.hpp" +#include "mrc/coroutines/sync_wait.hpp" +#include "mrc/coroutines/task.hpp" +#include "mrc/coroutines/when_all.hpp" + +#include +#include + +using namespace mrc; +using namespace mrc::channel; +using namespace mrc::channel::v2; + +class TestChannelV2 : public ::testing::Test +{ + protected: + void SetUp() override {} + + void TearDown() override {} + + ImmediateChannel m_channel; + + coroutines::Task int_writer(int iterations) + { + for (int i = 0; i < iterations; i++) + { + co_await m_channel.async_write(std::move(i)); + } + m_channel.close(); + co_return; + } + + coroutines::Task int_reader(int iterations) + { + int i = 0; + while (true) + { + auto data = co_await m_channel.async_read(); + if (!data) + { + break; + } + i++; + } + EXPECT_EQ(i, iterations); + co_return; + } +}; + +TEST_F(TestChannelV2, ChannelClosed) +{ + ImmediateChannel channel; + channel.close(); + + auto test = [&]() -> coroutines::Task { + // write should throw + EXPECT_ANY_THROW(co_await channel.async_write(42)); + + // read should return unexpected + auto data = co_await channel.async_read(); + EXPECT_FALSE(data); + + // task throws + co_await channel.async_write(42); + co_return; + }; + + EXPECT_ANY_THROW(coroutines::sync_wait(test())); +} + +TEST_F(TestChannelV2, SingleWriterSingleReader) +{ + coroutines::sync_wait(coroutines::when_all(int_writer(3), int_reader(3))); +} + +TEST_F(TestChannelV2, Readerx1_Writer_x1) +{ + coroutines::sync_wait(coroutines::when_all(int_reader(3), int_writer(3))); +} + +TEST_F(TestChannelV2, Readerx2_Writer_x1) +{ + coroutines::sync_wait(coroutines::when_all(int_reader(2), int_reader(1), int_writer(3))); +} + +TEST_F(TestChannelV2, Readerx3_Writer_x1) +{ + coroutines::sync_wait(coroutines::when_all(int_reader(1), int_reader(1), int_reader(1), int_writer(3))); +} + +TEST_F(TestChannelV2, Readerx4_Writer_x1) +{ + // reader are a lifo, so the first reader in the task list will not get a data entry + coroutines::sync_wait( + coroutines::when_all(int_reader(0), int_reader(1), int_reader(1), int_reader(1), int_writer(3))); +} + +TEST_F(TestChannelV2, Readerx3_Writer_x1_Reader_x1) +{ + coroutines::sync_wait( + coroutines::when_all(int_reader(1), int_reader(1), int_reader(1), int_writer(3), int_reader(0))); +} + +TEST_F(TestChannelV2, Writer_2_Reader_x2) +{ + coroutines::sync_wait(coroutines::when_all(int_writer(2), int_writer(2), int_reader(4), int_reader(0))); +} From 4738c748fb2528048aeae61034b24bdf7c29d1ef Mon Sep 17 00:00:00 2001 From: Ryan Olson Date: Mon, 26 Dec 2022 22:42:52 +0000 Subject: [PATCH 2/4] move internal::Expected -> core::Expected --- cpp/mrc/{src/internal => include/mrc/core}/expected.hpp | 9 +++++---- cpp/mrc/src/internal/control_plane/client.hpp | 2 +- .../control_plane/client/connections_manager.cpp | 2 +- .../src/internal/control_plane/client/state_manager.cpp | 2 +- .../control_plane/client/subscription_service.cpp | 2 +- cpp/mrc/src/internal/control_plane/proto_helpers.hpp | 2 +- cpp/mrc/src/internal/control_plane/server.hpp | 2 +- .../internal/control_plane/server/connection_manager.hpp | 2 +- .../control_plane/server/subscription_manager.hpp | 2 +- cpp/mrc/src/tests/test_expected.cpp | 3 +-- 10 files changed, 14 insertions(+), 14 deletions(-) rename cpp/mrc/{src/internal => include/mrc/core}/expected.hpp (94%) diff --git a/cpp/mrc/src/internal/expected.hpp b/cpp/mrc/include/mrc/core/expected.hpp similarity index 94% rename from cpp/mrc/src/internal/expected.hpp rename to cpp/mrc/include/mrc/core/expected.hpp index c23439a09..70d4d6103 100644 --- a/cpp/mrc/src/internal/expected.hpp +++ b/cpp/mrc/include/mrc/core/expected.hpp @@ -21,12 +21,13 @@ #include "mrc/utils/macros.hpp" #include "mrc/utils/string_utils.hpp" // IWYU pragma: export -namespace mrc::internal { +namespace mrc { enum class ErrorCode { Internal, Fatal, + ChannelClosed, }; class Error; @@ -42,9 +43,9 @@ class Error final : public std::exception public: template - static UnexpectedError create(ArgsT&&... args) + static auto create(ArgsT&&... args) -> decltype(auto) { - return UnexpectedError(Error(std::forward(args)...)); + return std23::unexpected(Error(std::forward(args)...)); } DEFAULT_MOVEABILITY(Error); @@ -92,4 +93,4 @@ using Expected = std23::expected; // NOLINT throw expected.error(); \ } -} // namespace mrc::internal +} // namespace mrc diff --git a/cpp/mrc/src/internal/control_plane/client.hpp b/cpp/mrc/src/internal/control_plane/client.hpp index a640fef34..16b8db778 100644 --- a/cpp/mrc/src/internal/control_plane/client.hpp +++ b/cpp/mrc/src/internal/control_plane/client.hpp @@ -18,12 +18,12 @@ #pragma once #include "internal/control_plane/client/instance.hpp" // IWYU pragma: keep -#include "internal/expected.hpp" #include "internal/grpc/client_streaming.hpp" #include "internal/grpc/stream_writer.hpp" #include "internal/resources/partition_resources_base.hpp" #include "internal/service.hpp" +#include "mrc/core/expected.hpp" #include "mrc/node/source_channel.hpp" #include "mrc/protos/architect.grpc.pb.h" #include "mrc/protos/architect.pb.h" diff --git a/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp b/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp index fb216e970..25f05ec2a 100644 --- a/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp +++ b/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp @@ -19,12 +19,12 @@ #include "internal/control_plane/client.hpp" #include "internal/control_plane/client/instance.hpp" -#include "internal/expected.hpp" #include "internal/runnable/resources.hpp" #include "internal/ucx/resources.hpp" #include "internal/ucx/worker.hpp" #include "internal/utils/contains.hpp" +#include "mrc/core/expected.hpp" #include "mrc/core/task_queue.hpp" #include "mrc/protos/architect.pb.h" diff --git a/cpp/mrc/src/internal/control_plane/client/state_manager.cpp b/cpp/mrc/src/internal/control_plane/client/state_manager.cpp index 0b8c0d9e0..4b6b4083a 100644 --- a/cpp/mrc/src/internal/control_plane/client/state_manager.cpp +++ b/cpp/mrc/src/internal/control_plane/client/state_manager.cpp @@ -18,9 +18,9 @@ #include "internal/control_plane/client/state_manager.hpp" #include "internal/control_plane/client.hpp" -#include "internal/expected.hpp" #include "internal/runnable/resources.hpp" +#include "mrc/core/expected.hpp" #include "mrc/node/edge_builder.hpp" #include "mrc/node/rx_sink.hpp" #include "mrc/node/source_channel.hpp" diff --git a/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp b/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp index 5e810883e..a34818b31 100644 --- a/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp +++ b/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp @@ -19,10 +19,10 @@ #include "internal/control_plane/client.hpp" #include "internal/control_plane/client/instance.hpp" -#include "internal/expected.hpp" #include "internal/service.hpp" #include "internal/utils/contains.hpp" +#include "mrc/core/expected.hpp" #include "mrc/protos/architect.pb.h" #include diff --git a/cpp/mrc/src/internal/control_plane/proto_helpers.hpp b/cpp/mrc/src/internal/control_plane/proto_helpers.hpp index f802c7b15..04421829a 100644 --- a/cpp/mrc/src/internal/control_plane/proto_helpers.hpp +++ b/cpp/mrc/src/internal/control_plane/proto_helpers.hpp @@ -17,7 +17,7 @@ #pragma once -#include "internal/expected.hpp" +#include "mrc/core/expected.hpp" #include diff --git a/cpp/mrc/src/internal/control_plane/server.hpp b/cpp/mrc/src/internal/control_plane/server.hpp index 0b93e1718..b873be9ee 100644 --- a/cpp/mrc/src/internal/control_plane/server.hpp +++ b/cpp/mrc/src/internal/control_plane/server.hpp @@ -18,11 +18,11 @@ #pragma once #include "internal/control_plane/server/connection_manager.hpp" -#include "internal/expected.hpp" #include "internal/grpc/server.hpp" #include "internal/grpc/server_streaming.hpp" #include "internal/service.hpp" +#include "mrc/core/expected.hpp" #include "mrc/node/queue.hpp" #include "mrc/protos/architect.grpc.pb.h" diff --git a/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp b/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp index 685843a6e..74cc60edf 100644 --- a/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp +++ b/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp @@ -18,9 +18,9 @@ #pragma once #include "internal/control_plane/server/versioned_issuer.hpp" -#include "internal/expected.hpp" #include "internal/grpc/server_streaming.hpp" +#include "mrc/core/expected.hpp" #include "mrc/types.hpp" #include diff --git a/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp b/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp index 35809eb8f..e68e4f7d5 100644 --- a/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp +++ b/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp @@ -19,8 +19,8 @@ #include "internal/control_plane/server/tagged_issuer.hpp" #include "internal/control_plane/server/versioned_issuer.hpp" -#include "internal/expected.hpp" +#include "mrc/core/expected.hpp" #include "mrc/types.hpp" #include diff --git a/cpp/mrc/src/tests/test_expected.cpp b/cpp/mrc/src/tests/test_expected.cpp index 366bfccae..8a4eb666d 100644 --- a/cpp/mrc/src/tests/test_expected.cpp +++ b/cpp/mrc/src/tests/test_expected.cpp @@ -15,7 +15,7 @@ * limitations under the License. */ -#include "internal/expected.hpp" +#include "mrc/core/expected.hpp" #include #include @@ -26,7 +26,6 @@ #include using namespace mrc; -using namespace mrc::internal; class TestExpected : public ::testing::Test {}; From cb51342980e0054e5327b74591abda8f30725838 Mon Sep 17 00:00:00 2001 From: Ryan Olson Date: Tue, 27 Dec 2022 05:26:54 +0000 Subject: [PATCH 3/4] std23::expected -> mrc::expected; renamed the opinionated Expected file -> error.hpp --- cpp/mrc/include/mrc/core/error.hpp | 91 + cpp/mrc/include/mrc/core/expected.hpp | 1489 ++++++++++++++++- cpp/mrc/include/mrc/core/std23_expected.hpp | 1481 ---------------- .../include/mrc/coroutines/ring_buffer.hpp | 6 +- cpp/mrc/src/internal/control_plane/client.hpp | 2 +- .../client/connections_manager.cpp | 2 +- .../control_plane/client/state_manager.cpp | 2 +- .../client/subscription_service.cpp | 2 +- .../internal/control_plane/proto_helpers.hpp | 2 +- cpp/mrc/src/internal/control_plane/server.cpp | 4 +- cpp/mrc/src/internal/control_plane/server.hpp | 2 +- .../server/connection_manager.hpp | 2 +- .../server/subscription_manager.hpp | 2 +- cpp/mrc/src/tests/test_expected.cpp | 2 +- 14 files changed, 1542 insertions(+), 1547 deletions(-) create mode 100644 cpp/mrc/include/mrc/core/error.hpp delete mode 100644 cpp/mrc/include/mrc/core/std23_expected.hpp diff --git a/cpp/mrc/include/mrc/core/error.hpp b/cpp/mrc/include/mrc/core/error.hpp new file mode 100644 index 000000000..c6ead312a --- /dev/null +++ b/cpp/mrc/include/mrc/core/error.hpp @@ -0,0 +1,91 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * 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 "mrc/core/expected.hpp" // IWYU pragma: export +#include "mrc/utils/macros.hpp" +#include "mrc/utils/string_utils.hpp" // IWYU pragma: export + +namespace mrc { + +enum class ErrorCode +{ + Internal, + Fatal, + ChannelClosed, +}; + +class Error final : public std::exception +{ + Error(ErrorCode type) : m_code(type) {} + Error(std::string message) : Error(ErrorCode::Internal, std::move(message)) {} + Error(ErrorCode type, std::string message) : m_code(type), m_message(std::move(message)) {} + + public: + template + static auto create(ArgsT&&... args) -> decltype(auto) + { + return unexpected(Error(std::forward(args)...)); + } + + DEFAULT_MOVEABILITY(Error); + DEFAULT_COPYABILITY(Error); + + ErrorCode code() const + { + return m_code; + } + const std::string& message() const + { + return m_message; + } + + const char* what() const noexcept final + { + return m_message.c_str(); + } + + private: + ErrorCode m_code; + std::string m_message; +}; + +template +using Expected = expected; // NOLINT + +#define MRC_CHECK(condition) \ + if (!(condition)) \ + { \ + return Error::create(MRC_CONCAT_STR("CHECK failed: " #condition "")); \ + } + +#define MRC_EXPECT(expected) \ + if (!(expected)) \ + { \ + DVLOG(10) << "expect failed: " << expected.error().message(); \ + return Error::create(std::move(expected.error())); \ + } + +#define MRC_THROW_ON_ERROR(expected) \ + if (!(expected)) \ + { \ + DVLOG(10) << "expect failed: " << expected.error().message(); \ + throw expected.error(); \ + } + +} // namespace mrc diff --git a/cpp/mrc/include/mrc/core/expected.hpp b/cpp/mrc/include/mrc/core/expected.hpp index 70d4d6103..2c20f58bf 100644 --- a/cpp/mrc/include/mrc/core/expected.hpp +++ b/cpp/mrc/include/mrc/core/expected.hpp @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,82 +15,1467 @@ * limitations under the License. */ +/** + * Original Source: https://github.com/RishabhRD/expected + * Original License: MIT, included below + */ + +/* + * MIT License + * + * Copyright (c) 2022 Rishabh Dwivedi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + #pragma once -#include "mrc/core/std23_expected.hpp" // IWYU pragma: export -#include "mrc/utils/macros.hpp" -#include "mrc/utils/string_utils.hpp" // IWYU pragma: export +#include +#include +#include +#include +#include +#include +#include +#include namespace mrc { -enum class ErrorCode -{ - Internal, - Fatal, - ChannelClosed, +// clang-format off +// NOLINTBEGIN(*) + +template +class unexpected { + public: + using value_type = E; + constexpr unexpected(unexpected const&) = default; + constexpr unexpected(unexpected&&) = default; // NOLINT + constexpr auto operator=(unexpected const&) -> unexpected& = default; + constexpr auto operator=(unexpected&&) -> unexpected& = default; // NOLINT + ~unexpected() = default; + + template + requires std::constructible_from + constexpr explicit unexpected(std::in_place_t /*unused*/, Args&&... args) + : val(std::forward(args)...) {} + + template + requires std::constructible_from < E, std::initializer_list + &, Args... > constexpr explicit unexpected(std::in_place_t /*unused*/, + std::initializer_list i_list, + Args&&... args) + : val(i_list, std::forward(args)...) {} + + template + requires + (!std::same_as, unexpected>) && + (!std::same_as, std::in_place_t>)&& + std::constructible_from + constexpr explicit unexpected(Err&& err) // NOLINT + : val(std::forward(err)) {} + + constexpr auto value() const& noexcept -> E const& { return val; } + constexpr auto value() & noexcept -> E& { return val; } + constexpr auto value() const&& noexcept -> E const&& { + return std::move(val); + } + constexpr auto value() && noexcept -> E&& { return std::move(val); } + + constexpr void swap(unexpected& other) noexcept( + std::is_nothrow_swappable_v) requires(std::is_swappable_v) { + using std::swap; + swap(val, other.val); + } + + template + requires(requires(E const& x, E2 const& y) { + { x == y } -> std::convertible_to; + }) + friend constexpr auto operator==(unexpected const& x, unexpected const& y) -> bool { + return x.value() == y.value(); + } + + friend constexpr void swap(unexpected& x, unexpected& y) + noexcept(noexcept(x.swap(y))) + requires(std::is_swappable_v) { + x.swap(y); + } + + private : E val; +}; + +template +unexpected(E) -> unexpected; + +template +class bad_expected_access; + +template <> +class bad_expected_access : public std::exception { + protected: + bad_expected_access() noexcept = default; + bad_expected_access(bad_expected_access const&) = default; + bad_expected_access(bad_expected_access&&) = default; + auto operator=(bad_expected_access const&) -> bad_expected_access& = default; + auto operator=(bad_expected_access&&) -> bad_expected_access& = default; + ~bad_expected_access() override = default; + + public: + auto what() const noexcept -> char const* override { // NOLINT + return "bad expected access"; + } +}; + +template +class bad_expected_access : public bad_expected_access { + public: + explicit bad_expected_access(E e) : val(std::move(e)) {} + auto what() const noexcept -> char const* override { // NOLINT + return "bad expected access"; + } + + auto error() & noexcept -> E& { return val; } + auto error() const& noexcept -> E const& { return val; } + auto error() && noexcept -> E&& { return std::move(val); } + auto error() const&& noexcept -> const E&& { return std::move(val); } + + private: + E val; }; -class Error; +struct unexpect_t {}; +inline constexpr unexpect_t unexpect{}; + +namespace detail { +template +concept non_void_destructible = std::same_as || std::destructible; +} + +template +class expected; + +namespace detail { +template +concept expected_constructible_from_other = + std::constructible_from && + std::constructible_from && + (!std::constructible_from&>)&& + (!std::constructible_from>)&& + (!std::constructible_from const&>)&& + (!std::constructible_from const>)&& + (!std::convertible_to&, T>)&& + (!std::convertible_to&&, T>)&& + (!std::convertible_to const&, T>)&& + (!std::convertible_to const&&, T>)&& + (!std::constructible_from, expected&>)&& + (!std::constructible_from, expected>)&& + (!std::constructible_from, expected const&>)&& + (!std::constructible_from, expected const>); + +template +concept is_unexpected = + std::same_as, unexpected>; + +template +concept is_expected = + std::same_as, + expected>; + +// This function makes sure expected doesn't get into valueless_by_exception +// state due to any exception while assignment +template +constexpr void reinit_expected(T& newval, U& oldval, Args&&... args) { + if constexpr (std::is_nothrow_constructible_v) { + std::destroy_at(std::addressof(oldval)); + std::construct_at(std::addressof(newval), std::forward(args)...); + } else if constexpr (std::is_nothrow_move_constructible_v) { + T tmp(std::forward(args)...); + std::destroy_at(std::addressof(oldval)); + std::construct_at(std::addressof(newval), std::move(tmp)); + } else { + U tmp(std::move(oldval)); + std::destroy_at(std::addressof(oldval)); + try { + std::construct_at(std::addressof(newval), std::forward(args)...); + } catch (...) { + std::construct_at(std::addressof(oldval), std::move(tmp)); + throw; + } + } +} + +} // namespace detail + +template +class expected { + public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + + template + using rebind = expected; + + // constructors + // postcondition: has_value() = true + constexpr expected() requires std::default_initializable : val{} {}; + + // postcondition: has_value() = rhs.has_value() + constexpr expected(expected const& rhs) + requires std::copy_constructible && std::copy_constructible && + std::is_trivially_copy_constructible_v && + std::is_trivially_copy_constructible_v + = default; + + // postcondition: has_value() = rhs.has_value() + constexpr expected(expected const& rhs) + requires std::copy_constructible && std::copy_constructible + : has_val(rhs.has_val) { + if (rhs.has_value()) { + std::construct_at(std::addressof(this->val), *rhs); + } else { + std::construct_at(std::addressof(this->unex), rhs.error()); + } + } + + constexpr expected(expected&&) noexcept( + std::is_nothrow_move_constructible_v&& + std::is_nothrow_move_constructible_v) requires + std::move_constructible && std::move_constructible && + std::is_trivially_move_constructible_v && + std::is_trivially_move_constructible_v + = default; + + constexpr expected(expected&& rhs) noexcept( + std::is_nothrow_move_constructible_v&& + std::is_nothrow_move_constructible_v) requires + std::move_constructible && std::move_constructible + : has_val(rhs.has_value()) { + if (rhs.has_value()) { + std::construct_at(std::addressof(this->val), std::move(*rhs)); + } else { + std::construct_at(std::addressof(this->unex), std::move(rhs.error())); + } + } + + template + requires detail::expected_constructible_from_other + constexpr explicit(!std::convertible_to || + !std::convertible_to) + expected(expected const& rhs) // NOLINT + : has_val(rhs.has_value()) { + using UF = U const&; + using GF = G const&; + if (rhs.has_value()) { + std::construct_at(std::addressof(this->val), std::forward(*rhs)); + } else { + std::construct_at(std::addressof(this->unex), + std::forward(rhs.error())); + } + } + + template + requires detail::expected_constructible_from_other + constexpr explicit(!std::convertible_to || !std::convertible_to) + expected(expected&& rhs) // NOLINT + : has_val(rhs.has_value()) { + using UF = U const&; + using GF = G const&; + if (rhs.has_value()) { + std::construct_at(std::addressof(this->val), std::forward(*rhs)); + } else { + std::construct_at(std::addressof(this->unex), + std::forward(rhs.error())); + } + } + + template + requires(!std::same_as, std::in_place_t>) && + (!std::same_as, std::remove_cvref_t>)&& + (!detail::is_unexpected)&& + std::constructible_from + constexpr explicit(!std::convertible_to) expected(U&& v) // NOLINT + : val(std::forward(v)) {} + + template + requires std::constructible_from + constexpr explicit(!std::convertible_to) + expected(unexpected const& e) // NOLINT + : has_val{false}, unex(std::forward(e.value())) {} + + template + requires std::constructible_from + constexpr explicit(!std::convertible_to) + expected(unexpected&& e) // NOLINT + : has_val{false}, unex(std::forward(e.value())) {} + + template + requires std::constructible_from + constexpr explicit expected(std::in_place_t /*unused*/, Args&&... args) + : val(std::forward(args)...) {} + + template + requires std::constructible_from < T, std::initializer_list + &, Args... > constexpr explicit expected(std::in_place_t /*unused*/, + std::initializer_list il, + Args&&... args) + : val(il, std::forward(args)...) {} + + template + requires std::constructible_from + constexpr explicit expected(unexpect_t /*unused*/, Args&&... args) + : has_val{false}, unex(std::forward(args)...) {} + + template + requires std::constructible_from < E, std::initializer_list + &, + Args... > constexpr explicit expected(unexpect_t /*unused*/, + std::initializer_list il, + Args&&... args) + : has_val(false), + unex(il, std::forward(args)...) {} + + // destructor + constexpr ~expected() { + if constexpr (std::is_trivially_destructible_v and + std::is_trivially_destructible_v) { + } else if constexpr (std::is_trivially_destructible_v) { + if (!has_val) { + std::destroy_at(std::addressof(this->unex)); + } + } else if constexpr (std::is_trivially_destructible_v) { + if (has_val) { + std::destroy_at(std::addressof(this->val)); + } + } else { + if (has_val) { + std::destroy_at(std::addressof(this->val)); + } else { + std::destroy_at(std::addressof(this->unex)); + } + } + } + + // assignment + constexpr auto operator=(expected const& rhs) // NOLINT + -> expected& requires 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) + { + has_val = rhs.has_value(); + if (this->has_value() and rhs.has_value()) { + this->val = *rhs; + } else if (this->has_value()) { + detail::reinit_expected(this->unex, this->val, rhs.error()); + } else if (rhs.has_value()) { + detail::reinit_expected(this->val, this->unex, *rhs); + } else { + this->unex = rhs.error(); + } + return *this; + } + + constexpr auto operator=(expected&& rhs) // + noexcept(std::is_nothrow_move_assignable_v&& + std::is_nothrow_move_constructible_v&& + std::is_nothrow_move_assignable_v&& + std::is_nothrow_move_constructible_v) + -> expected& requires std::is_move_constructible_v && + std::is_move_assignable_v && + std::is_move_constructible_v && + std::is_move_assignable_v && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v) + { + has_val = rhs.has_value(); + if (this->has_value() and rhs.has_value()) { + this->val = std::move(*rhs); + } else if (this->has_value()) { + detail::reinit_expected(this->unex, this->val, std::move(rhs.error())); + } else if (rhs.has_value()) { + detail::reinit_expected(this->val, this->unex, std::move(*rhs)); + } else { + this->unex = std::move(rhs.error()); + } + return *this; + } + + template + constexpr auto operator=(U&& rhs) -> expected& + requires + (!std::same_as>) && + (!detail::is_unexpected>)&& + std::constructible_from&& + std::is_assignable_v && + ( std::is_nothrow_constructible_v || + std::is_nothrow_move_constructible_v || + std::is_nothrow_move_constructible_v + ) { + if (this->has_value()) { + this->val = std::forward(rhs); + return *this; + } + detail::reinit_expected(this->val, this->unex, std::forward(rhs)); + has_val = true; + return *this; + } + + template + requires std::constructible_from && + std::is_assignable_v && + (std::is_nothrow_constructible_v || + std::is_nothrow_move_constructible_v || + std::is_nothrow_move_constructible_v + ) + constexpr auto operator=(unexpected const& e) -> expected& { + using GF = G const&; + if (has_value()) { + detail::reinit_expected(this->unex, this->val, + std::forward(e.value())); + } else { + this->unex = std::forward(e.value()); + } + has_val = false; + return *this; + } + + template + requires std::constructible_from && + std::is_assignable_v && + (std::is_nothrow_constructible_v || + std::is_nothrow_move_constructible_v || + std::is_nothrow_move_constructible_v + ) + constexpr auto operator=(unexpected&& e) -> expected& { + using GF = G; + if (has_value()) { + detail::reinit_expected(this->unex, this->val, + std::forward(e.value())); + } else { + this->unex = std::forward(e.value()); + } + has_val = false; + return *this; + } + + // modifiers + template + requires std::is_nothrow_constructible_v + constexpr auto emplace(Args&&... args) noexcept -> T& { + if (has_value()) { + std::destroy_at(std::addressof(this->val)); + } else { + std::destroy_at(std::addressof(this->unex)); + has_val = true; + } + return *std::construct_at(std::addressof(this->val), + std::forward(args)...); + } + + template + requires std::is_nothrow_constructible_v < T, std::initializer_list &, Args...> + constexpr auto emplace(std::initializer_list il, + Args&&... args) noexcept -> T& { + if (has_value()) { + std::destroy_at(std::addressof(this->val)); + } else { + std::destroy_at(std::addressof(this->unex)); + has_val = true; + } + return *std::construct_at(std::addressof(this->val), il, + std::forward(args)...); + } + + // swap + constexpr void swap(expected& rhs) noexcept( + std::is_nothrow_constructible_v&& + std::is_nothrow_swappable_v&& + std::is_nothrow_move_constructible_v&& + std::is_nothrow_swappable_v) + requires std::is_swappable_v && + std::is_swappable_v && + std::is_move_constructible_v && + std::is_move_constructible_v && + (std::is_nothrow_constructible_v || + std::is_nothrow_constructible_v) + { + if (rhs.has_value()) { + if (has_value()) { + using std::swap; + swap(this->val, rhs.val); + } else { + rhs.swap(*this); + } + } else { + if (has_value()) { + if constexpr (std::is_nothrow_move_constructible_v) { + E tmp(std::move(rhs.unex)); + std::destroy_at(std::addressof(rhs.unex)); + try { + std::construct_at(std::addressof(rhs.val), std::move(this->val)); + std::destroy_at(std::addressof(this->val)); + std::construct_at(std::addressof(this->unex), std::move(tmp)); + } catch (...) { + std::construct_at(std::addressof(rhs.unex), std::move(tmp)); + throw; + } + } else { + T tmp(std::move(this->val)); + std::destroy_at(std::addressof(this->val)); + try { + std::construct_at(std::addressof(this->unex), std::move(rhs.unex)); + std::destroy_at(std::addressof(rhs.unex)); + std::construct_at(std::addressof(rhs.val), std::move(tmp)); + } catch (...) { + std::construct_at(std::addressof(this->val), std::move(tmp)); + throw; + } + } + has_val = false; + rhs.has_val = true; + } else { + using std::swap; + swap(this->unex, rhs.unex); + } + } + } + + // observers + + // precondition: has_value() = true + constexpr auto operator->() const noexcept -> T const* { + return std::addressof(this->val); + } + + // precondition: has_value() = true + constexpr auto operator->() noexcept -> T* { + return std::addressof(this->val); + } + + // precondition: has_value() = true + constexpr auto operator*() const& noexcept -> T const& { return this->val; } + + // precondition: has_value() = true + constexpr auto operator*() & noexcept -> T& { return this->val; } + + // precondition: has_value() = true + constexpr auto operator*() const&& noexcept -> T const&& { + return std::move(this->val); + } + + // precondition: has_value() = true + constexpr auto operator*() && noexcept -> T&& { return std::move(this->val); } + + constexpr explicit operator bool() const noexcept { return has_val; } + + [[nodiscard]] constexpr auto has_value() const noexcept -> bool { + return has_val; + } + + constexpr auto value() const& -> T const& { + if (has_value()) { + return this->val; + } + throw bad_expected_access(error()); + } + + constexpr auto value() & -> T& { + if (has_value()) { + return this->val; + } + throw bad_expected_access(error()); + } + + constexpr auto value() const&& -> T const&& { + if (has_value()) { + return std::move(this->val); + } + throw bad_expected_access(std::move(error())); + } + + constexpr auto value() && -> T&& { + if (has_value()) { + return std::move(this->val); + } + throw bad_expected_access(std::move(error())); + } + + // precondition: has_value() = false + constexpr auto error() const& -> E const& { return this->unex; } + + // precondition: has_value() = false + constexpr auto error() & -> E& { return this->unex; } + + // precondition: has_value() = false + constexpr auto error() const&& -> E const&& { return std::move(this->unex); } + + // precondition: has_value() = false + constexpr auto error() && -> E&& { return std::move(this->unex); } + + template + requires + std::is_copy_constructible_v && std::is_convertible_v + constexpr auto value_or(U&& v) const& -> T { + return has_value() ? **this : static_cast(std::forward(v)); + } + + template + requires std::is_move_constructible_v && std::is_convertible_v + constexpr auto value_or(U&& v) && -> T { + return has_value() ? std::move(**this) : static_cast(std::forward(v)); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v && std::is_copy_constructible_v + constexpr auto and_then(F&& f) & { + if (has_value()) { + return std::invoke(std::forward(f), **this); + } + return U(unexpect, error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto and_then(F&& f) const& { + if (has_value()) { + return std::invoke(std::forward(f), **this); + } + return U(unexpect, error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto and_then(F&& f) && { + if (has_value()) { + return std::invoke(std::forward(f), std::move(**this)); + } + return U(unexpect, std::move(error())); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto and_then(F&& f) const&& { + if (has_value()) { + return std::invoke(std::forward(f), std::move(**this)); + } + return U(unexpect, std::move(error())); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) & { + if (has_value()) { + return G(**this); + } + return std::invoke(std::forward(f), error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) const& { + if (has_value()) { + return G(**this); + } + return std::invoke(std::forward(f), error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto or_else(F&& f) && { + if (has_value()) { + return G(std::move(**this)); + } + return std::invoke(std::forward(f), std::move(error())); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto or_else(F&& f) const&& { + if (has_value()) { + return G(std::move(**this)); + } + return std::invoke(std::forward(f), std::move(error())); + } + + template >> + requires std::is_void_v && + std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) & { + if (has_value()) { + return expected(*this); + } + std::invoke(std::forward(f), error()); + return expected(*this); + } -// todo(#219) - update tidy to allow the following typedef -using UnexpectedError = std23::unexpected; // NOLINT + template >> + requires std::is_void_v && + std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) const& { + if (has_value()) { + return expected(*this); + } + std::invoke(std::forward(f), error()); + return expected(*this); + } -class Error final : public std::exception -{ - Error(ErrorCode type) : m_code(type) {} - Error(std::string message) : Error(ErrorCode::Internal, std::move(message)) {} - Error(ErrorCode type, std::string message) : m_code(type), m_message(std::move(message)) {} + template >> + requires std::is_void_v && + std::is_move_constructible_v && + std::is_move_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) && { + if (has_value()) { + return expected(std::move(*this)); + } + // TODO: is this copy necessary, as f can be just read argument function + std::invoke(std::forward(f), error()); + return expected(std::move(*this)); + } - public: - template - static auto create(ArgsT&&... args) -> decltype(auto) - { - return std23::unexpected(Error(std::forward(args)...)); + template >> + requires std::is_void_v && + std::is_move_constructible_v && + std::is_move_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) const&& { + if (!has_value()) { + return expected(std::move(*this)); } + // TODO: is this copy necessary, as f can be just read argument function + std::invoke(std::forward(f), error()); + return expected(std::move(*this)); + } - DEFAULT_MOVEABILITY(Error); - DEFAULT_COPYABILITY(Error); + template >> + requires std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto transform(F&& f) & { + if (has_value()) { + if constexpr (!std::same_as) { + return expected(std::invoke(std::forward(f), **this)); + } else { + std::invoke(std::forward(f), std::move(**this)); + return expected(); + } + } + return expected(unexpect, error()); + } - ErrorCode code() const - { - return m_code; + template >> + requires std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto transform(F&& f) const& { + if (has_value()) { + if constexpr (!std::same_as) { + return expected(std::invoke(std::forward(f), **this)); + } else { + std::invoke(std::forward(f), std::move(**this)); + return expected(); + } } - const std::string& message() const - { - return m_message; + return expected(unexpect, error()); + } + + template >> + requires std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto transform(F&& f) && { + if (has_value()) { + if constexpr (!std::same_as) { + return expected( + std::invoke(std::forward(f), std::move(**this))); + } else { + std::invoke(std::forward(f), std::move(**this)); + return expected(); + } } + return expected(unexpect, std::move(error())); + } - const char* what() const noexcept final - { - return m_message.c_str(); + template >> + requires std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto transform(F&& f) const&& { + if (has_value()) { + if constexpr (!std::same_as) { + return expected( + std::invoke(std::forward(f), std::move(**this))); + } else { + std::invoke(std::forward(f), std::move(**this)); + return expected(); + } } + return expected(unexpect, std::move(error())); + } - private: - ErrorCode m_code; - std::string m_message; + template >> + requires std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto transform_error(F&& f) & { + if (has_value()) { + return expected(**this); + } + return expected(unexpect, std::invoke(std::forward(f), error())); + } + + template >> + requires std::is_copy_constructible_v && + std::is_copy_constructible_v + constexpr auto transform_error(F&& f) const& { + if (has_value()) { + return expected(**this); + } + return expected(unexpect, std::invoke(std::forward(f), error())); + } + + template >> + requires std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto transform_error(F&& f) && { + if (has_value()) { + return expected(std::move(**this)); + } + return expected(unexpect, + std::invoke(std::forward(f), std::move(error()))); + } + + template >> + requires std::is_move_constructible_v && + std::is_move_constructible_v + constexpr auto transform_error(F&& f) const&& { + if (has_value()) { + return expected(std::move(**this)); + } + return expected(unexpect, + std::invoke(std::forward(f), std::move(error()))); + } + + // equality operators + template + requires(!std::is_void_v) && + requires(T const& t1, T2 const& t2, E const& e1, E2 const& e2) { + { t1 == t2 } -> std::convertible_to; + { e1 == e2 } -> std::convertible_to; + } + friend constexpr auto operator==(expected const& x, expected const& y) + -> bool { + if (x.has_value() != y.has_value()) { + return false; + } + return x.has_value() ? (*x == *y) : (x.error() == y.error()); + } + + template + requires(!detail::is_expected) && requires(T const& x, T2 const& v) { + { x == v } -> std::convertible_to; + } + friend constexpr auto operator==(expected const& x, T2 const& v) -> bool { + return x.has_value() && static_cast(*x == v); + } + + template + requires requires(E const& x, unexpected const& e) { + { x == e.value() } -> std::convertible_to; + } + friend constexpr auto operator==(expected const& x, unexpected const& e) + -> bool { + return !x.has_value() && static_cast(x.error() == e.value()); + } + + // specialized algorithms + friend constexpr void swap(expected& x, + expected& y) noexcept(noexcept(x.swap(y))) { + x.swap(y); + } + + private: + bool has_val{true}; + union { + T val; + E unex; + }; }; -template -using Expected = std23::expected; // NOLINT +template +class expected { + public: + using value_type = void; + using error_type = E; + using unexpected_type = unexpected; + + template + using rebind = expected; + + // constructors + + // postcondition: has_value() = true + constexpr expected() noexcept {} // NOLINT -#define MRC_CHECK(condition) \ - if (!(condition)) \ - { \ - return Error::create(MRC_CONCAT_STR("CHECK failed: " #condition "")); \ + constexpr expected( + expected const& rhs) requires std::is_copy_constructible_v && + std::is_trivially_copy_constructible_v + = default; + + constexpr expected( + expected const& rhs) requires std::is_copy_constructible_v + : has_val(rhs.has_value()) { + if (!rhs.has_value()) { + std::construct_at(std::addressof(this->unex), rhs.error()); + } + } + + constexpr expected(expected&&) + noexcept(std::is_nothrow_move_constructible_v) + requires std::is_move_constructible_v && + std::is_trivially_move_constructible_v + = default; + + constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v) + requires std::is_move_constructible_v : has_val(rhs.has_value()) { + if (!rhs.has_value()) { + std::construct_at(std::addressof(this->unex), std::move(rhs.error())); + } + } + + template + requires std::is_void_v + && std::is_constructible_v + &&(!std::is_constructible_v, expected&>) + &&(!std::is_constructible_v, expected>) + &&(!std::is_constructible_v, expected const&>) + &&(!std::is_constructible_v, expected const&>) + constexpr explicit(!std::is_convertible_v) + expected(expected const& rhs) // NOLINT + : has_val(rhs.has_value()) { + if (!rhs.has_value()) { + std::construct_at(std::addressof(this->unex), + std::forward(rhs.error())); + } + } + + template + requires std::is_void_v + && std::is_constructible_v + &&(!std::is_constructible_v, expected&>) + &&(!std::is_constructible_v, expected>) + &&(!std::is_constructible_v, expected const&>) + &&(!std::is_constructible_v, expected const&>) + constexpr explicit(!std::is_convertible_v) + expected(expected&& rhs) // NOLINT + : has_val(rhs.has_value()) { + if (!rhs.has_value()) { + std::construct_at(std::addressof(this->unex), + std::forward(rhs.error())); } + } + + template + requires std::is_constructible_v + constexpr explicit(!std::is_convertible_v) + expected(unexpected const& e) // NOLINT + : has_val(false), unex(std::forward(e.value())) {} + + template + requires std::is_constructible_v + constexpr explicit(!std::is_convertible_v) + expected(unexpected&& e) // NOLINT + : has_val(false), unex(std::forward(e.value())) {} + + constexpr explicit expected(std::in_place_t /*unused*/) noexcept {} -#define MRC_EXPECT(expected) \ - if (!(expected)) \ - { \ - DVLOG(10) << "expect failed: " << expected.error().message(); \ - return Error::create(std::move(expected.error())); \ + template + requires std::is_constructible_v + constexpr explicit expected(unexpect_t /*unused*/, Args&&... args) + : has_val(false), unex(std::forward(args)...) {} + + template + requires std::is_constructible_v &, Args...> + constexpr explicit expected(unexpect_t /*unused*/, std::initializer_list il, Args... args) + : has_val(false), + unex(il, std::forward(args)...) {} + + // destructor + constexpr ~expected() { + if constexpr (std::is_trivially_destructible_v) { + } else { + if (!has_value()) std::destroy_at(std::addressof(this->unex)); } + } -#define MRC_THROW_ON_ERROR(expected) \ - if (!(expected)) \ - { \ - DVLOG(10) << "expect failed: " << expected.error().message(); \ - throw expected.error(); \ + // assignment + constexpr auto operator=(expected const& rhs) -> expected& // NOLINT + requires std::is_copy_assignable_v && + std::is_copy_constructible_v { + if (has_value() && rhs.has_value()) { + } else if (has_value()) { + std::construct_at(std::addressof(this->unex), rhs.unex); + has_val = false; + } else if (rhs.has_value()) { + std::destroy_at(std::addressof(this->unex)); + has_val = true; + } else { + this->unex = rhs.error(); } + return *this; + } + + constexpr auto operator=(expected&& rhs) + noexcept(std::is_nothrow_move_constructible_v&& + std::is_nothrow_move_assignable_v) -> expected& + requires std::is_move_constructible_v && + std::is_move_assignable_v { + if (has_value() && rhs.has_value()) { + } else if (has_value()) { + std::construct_at(std::addressof(this->unex), std::move(rhs.unex)); + has_val = false; + } else if (rhs.has_value()) { + std::destroy_at(std::addressof(this->unex)); + has_val = true; + } else { + this->unex = std::move(rhs.error()); + } + return *this; + } + + template + requires std::is_constructible_v and + std::is_assignable_v + constexpr auto operator=(unexpected const& e) -> expected& { + if (has_value()) { + std::construct_at(std::addressof(this->unex), + std::forward(e.value())); + has_val = false; + } else { + this->unex = std::forward(e.value()); + } + return *this; + } + + template + requires std::is_constructible_v && + std::is_assignable_v + constexpr auto operator=(unexpected&& e) -> expected& { + if (has_value()) { + std::construct_at(std::addressof(this->unex), std::forward(e.value())); + has_val = false; + } else { + this->unex = std::forward(e.value()); + } + return *this; + } + + // modifiers + constexpr void emplace() noexcept { + if (!has_value()) { + std::destroy_at(std::addressof(this->unex)); + has_val = true; + } + } + + // swap + constexpr void swap(expected& rhs) + noexcept(std::is_nothrow_move_constructible_v&& + std::is_nothrow_swappable_v) + requires std::is_swappable_v && + std::is_move_constructible_v + { + if (rhs.has_value()) { + if (has_value()) { + } else { + rhs.swap(*this); + } + } else { + if (has_value()) { + std::construct_at(std::addressof(this->unex), std::move(rhs.unex)); + std::destroy_at(std::addressof(rhs.unex)); + has_val = false; + rhs.has_val = true; + } else { + using std::swap; + swap(this->unex, rhs.unex); + } + } + } + + // observers + constexpr explicit operator bool() const noexcept { return has_val; } + + [[nodiscard]] constexpr auto has_value() const noexcept -> bool { + return has_val; + } + + // precondition: has_value() = true + constexpr void operator*() const noexcept {} + + constexpr void value() const& { + if (!has_value()) { + throw bad_expected_access(error()); + } + } + + constexpr void value() && { + if (!has_value()) { + throw bad_expected_access(std::move(error())); + } + } + + // precondition: has_value() = false + constexpr auto error() const& -> E const& { return this->unex; } + + // precondition: has_value() = false + constexpr auto error() & -> E& { return this->unex; } + + // precondition: has_value() = false + constexpr auto error() const&& -> E const&& { return std::move(this->unex); } + + // precondition: has_value() = false + constexpr auto error() && -> E&& { return std::move(this->unex); } + + // monadic + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v + constexpr auto and_then(F&& f) & { + if (has_value()) { + return std::invoke(std::forward(f)); + } + return U(unexpect, error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v + constexpr auto and_then(F&& f) const& { + if (has_value()) { + return std::invoke(std::forward(f)); + } + return U(unexpect, error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v + constexpr auto and_then(F&& f) && { + if (has_value()) { + return std::invoke(std::forward(f)); + } + return U(unexpect, std::move(error())); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v + constexpr auto and_then(F&& f) const&& { + if (has_value()) { + return std::invoke(std::forward(f)); + } + return U(unexpect, std::move(error())); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) & { + if (has_value()) { + return G{}; + } + return std::invoke(std::forward(f), error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) const& { + if (has_value()) { + return G{}; + } + return std::invoke(std::forward(f), error()); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v + constexpr auto or_else(F&& f) && { + if (has_value()) { + return G{}; + } + return std::invoke(std::forward(f), std::move(error())); + } + + template >> + requires detail::is_expected && + std::is_same_v && + std::is_move_constructible_v + constexpr auto or_else(F&& f) const&& { + if (has_value()) { + return G{}; + } + return std::invoke(std::forward(f), std::move(error())); + } + + template >> + requires std::is_void_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) & { + if (has_value()) { + return expected(*this); + } + std::invoke(std::forward(f), error()); + return expected(*this); + } + + template >> + requires std::is_void_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) const& { + if (has_value()) { + return expected(*this); + } + std::invoke(std::forward(f), error()); + return expected(*this); + } + + template >> + requires std::is_void_v && + std::is_move_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) && { + if (has_value()) { + return expected(std::move(*this)); + } + // TODO: is this copy necessary, as f can be just read argument function + std::invoke(std::forward(f), error()); + return expected(std::move(*this)); + } + + template >> + requires std::is_void_v && + std::is_move_constructible_v && + std::is_copy_constructible_v + constexpr auto or_else(F&& f) const&& { + if (!has_value()) { + return expected(std::move(*this)); + } + // TODO: is this copy necessary, as f can be just read argument function + std::invoke(std::forward(f), error()); + return expected(std::move(*this)); + } + + template >> + requires std::is_copy_constructible_v + constexpr auto transform(F&& f) & { + if (has_value()) { + if constexpr (!std::same_as) { + return expected(std::invoke(std::forward(f))); + } else { + std::invoke(std::forward(f)); + return expected(); + } + } + return expected(unexpect, error()); + } + + template >> + requires std::is_copy_constructible_v + constexpr auto transform(F&& f) const& { + if (has_value()) { + if constexpr (!std::same_as) { + return expected(std::invoke(std::forward(f))); + } else { + std::invoke(std::forward(f)); + return expected(); + } + } + return expected(unexpect, error()); + } + + template >> + requires std::is_move_constructible_v + constexpr auto transform(F&& f) && { + if (has_value()) { + if constexpr (!std::same_as) { + return expected(std::invoke(std::forward(f))); + } else { + std::invoke(std::forward(f)); + return expected(); + } + } + return expected(unexpect, std::move(error())); + } + + template >> + requires std::is_move_constructible_v + constexpr auto transform(F&& f) const&& { + if (has_value()) { + if constexpr (!std::same_as) { + return expected(std::invoke(std::forward(f))); + } else { + std::invoke(std::forward(f)); + return expected(); + } + } + return expected(unexpect, std::move(error())); + } + + template >> + requires std::is_copy_constructible_v + constexpr auto transform_error(F&& f) & { + if (has_value()) { + return expected{}; + } + return expected(unexpect, + std::invoke(std::forward(f), error())); + } + + template >> + requires std::is_copy_constructible_v + constexpr auto transform_error(F&& f) const& { + if (has_value()) { + return expected{}; + } + return expected(unexpect, + std::invoke(std::forward(f), error())); + } + + template >> + requires std::is_move_constructible_v + constexpr auto transform_error(F&& f) && { + if (has_value()) { + return expected{}; + } + return expected( + unexpect, std::invoke(std::forward(f), std::move(error()))); + } + + template >> + requires std::is_move_constructible_v + constexpr auto transform_error(F&& f) const&& { + if (has_value()) { + return expected{}; + } + return expected( + unexpect, std::invoke(std::forward(f), std::move(error()))); + } + + // expected equality operators + template + requires std::is_void_v && requires(E e, E2 e2) { + { e == e2 } -> std::convertible_to; + } + friend constexpr auto operator==(expected const& x, expected const& y) + -> bool { + if (x.has_value() != y.has_value()) return false; + return x.has_value() or static_cast(x.error() == y.error()); + } + + template + requires requires(expected const& x, unexpected const& e) { + { x.error() == e.value() } -> std::convertible_to; + } + friend constexpr auto operator==(expected const& x, unexpected const& e) + -> bool { + return !x.has_value() && static_cast(x.error() == e.value()); + } + + // specialized algorithms + friend constexpr void swap(expected& x, + expected& y) noexcept(noexcept(x.swap(y))) { + x.swap(y); + } + + private: + bool has_val{true}; + union { + E unex; + }; +}; + +// NOLINTEND(*) +// clang-format on } // namespace mrc diff --git a/cpp/mrc/include/mrc/core/std23_expected.hpp b/cpp/mrc/include/mrc/core/std23_expected.hpp deleted file mode 100644 index 20580da29..000000000 --- a/cpp/mrc/include/mrc/core/std23_expected.hpp +++ /dev/null @@ -1,1481 +0,0 @@ -/** - * SPDX-FileCopyrightText: Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ - -/** - * Original Source: https://github.com/RishabhRD/expected - * Original License: MIT, included below - */ - -/* - * MIT License - * - * Copyright (c) 2022 Rishabh Dwivedi - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace std23 { - -// clang-format off -// NOLINTBEGIN(*) - -template -class unexpected { - public: - using value_type = E; - constexpr unexpected(unexpected const&) = default; - constexpr unexpected(unexpected&&) = default; // NOLINT - constexpr auto operator=(unexpected const&) -> unexpected& = default; - constexpr auto operator=(unexpected&&) -> unexpected& = default; // NOLINT - ~unexpected() = default; - - template - requires std::constructible_from - constexpr explicit unexpected(std::in_place_t /*unused*/, Args&&... args) - : val(std::forward(args)...) {} - - template - requires std::constructible_from < E, std::initializer_list - &, Args... > constexpr explicit unexpected(std::in_place_t /*unused*/, - std::initializer_list i_list, - Args&&... args) - : val(i_list, std::forward(args)...) {} - - template - requires - (!std::same_as, unexpected>) && - (!std::same_as, std::in_place_t>)&& - std::constructible_from - constexpr explicit unexpected(Err&& err) // NOLINT - : val(std::forward(err)) {} - - constexpr auto value() const& noexcept -> E const& { return val; } - constexpr auto value() & noexcept -> E& { return val; } - constexpr auto value() const&& noexcept -> E const&& { - return std::move(val); - } - constexpr auto value() && noexcept -> E&& { return std::move(val); } - - constexpr void swap(unexpected& other) noexcept( - std::is_nothrow_swappable_v) requires(std::is_swappable_v) { - using std::swap; - swap(val, other.val); - } - - template - requires(requires(E const& x, E2 const& y) { - { x == y } -> std::convertible_to; - }) - friend constexpr auto operator==(unexpected const& x, unexpected const& y) -> bool { - return x.value() == y.value(); - } - - friend constexpr void swap(unexpected& x, unexpected& y) - noexcept(noexcept(x.swap(y))) - requires(std::is_swappable_v) { - x.swap(y); - } - - private : E val; -}; - -template -unexpected(E) -> unexpected; - -template -class bad_expected_access; - -template <> -class bad_expected_access : public std::exception { - protected: - bad_expected_access() noexcept = default; - bad_expected_access(bad_expected_access const&) = default; - bad_expected_access(bad_expected_access&&) = default; - auto operator=(bad_expected_access const&) -> bad_expected_access& = default; - auto operator=(bad_expected_access&&) -> bad_expected_access& = default; - ~bad_expected_access() override = default; - - public: - auto what() const noexcept -> char const* override { // NOLINT - return "bad expected access"; - } -}; - -template -class bad_expected_access : public bad_expected_access { - public: - explicit bad_expected_access(E e) : val(std::move(e)) {} - auto what() const noexcept -> char const* override { // NOLINT - return "bad expected access"; - } - - auto error() & noexcept -> E& { return val; } - auto error() const& noexcept -> E const& { return val; } - auto error() && noexcept -> E&& { return std::move(val); } - auto error() const&& noexcept -> const E&& { return std::move(val); } - - private: - E val; -}; - -struct unexpect_t {}; -inline constexpr unexpect_t unexpect{}; - -namespace detail { -template -concept non_void_destructible = std::same_as || std::destructible; -} - -template -class expected; - -namespace detail { -template -concept expected_constructible_from_other = - std::constructible_from && - std::constructible_from && - (!std::constructible_from&>)&& - (!std::constructible_from>)&& - (!std::constructible_from const&>)&& - (!std::constructible_from const>)&& - (!std::convertible_to&, T>)&& - (!std::convertible_to&&, T>)&& - (!std::convertible_to const&, T>)&& - (!std::convertible_to const&&, T>)&& - (!std::constructible_from, expected&>)&& - (!std::constructible_from, expected>)&& - (!std::constructible_from, expected const&>)&& - (!std::constructible_from, expected const>); - -template -concept is_unexpected = - std::same_as, unexpected>; - -template -concept is_expected = - std::same_as, - expected>; - -// This function makes sure expected doesn't get into valueless_by_exception -// state due to any exception while assignment -template -constexpr void reinit_expected(T& newval, U& oldval, Args&&... args) { - if constexpr (std::is_nothrow_constructible_v) { - std::destroy_at(std::addressof(oldval)); - std::construct_at(std::addressof(newval), std::forward(args)...); - } else if constexpr (std::is_nothrow_move_constructible_v) { - T tmp(std::forward(args)...); - std::destroy_at(std::addressof(oldval)); - std::construct_at(std::addressof(newval), std::move(tmp)); - } else { - U tmp(std::move(oldval)); - std::destroy_at(std::addressof(oldval)); - try { - std::construct_at(std::addressof(newval), std::forward(args)...); - } catch (...) { - std::construct_at(std::addressof(oldval), std::move(tmp)); - throw; - } - } -} - -} // namespace detail - -template -class expected { - public: - using value_type = T; - using error_type = E; - using unexpected_type = unexpected; - - template - using rebind = expected; - - // constructors - // postcondition: has_value() = true - constexpr expected() requires std::default_initializable : val{} {}; - - // postcondition: has_value() = rhs.has_value() - constexpr expected(expected const& rhs) - requires std::copy_constructible && std::copy_constructible && - std::is_trivially_copy_constructible_v && - std::is_trivially_copy_constructible_v - = default; - - // postcondition: has_value() = rhs.has_value() - constexpr expected(expected const& rhs) - requires std::copy_constructible && std::copy_constructible - : has_val(rhs.has_val) { - if (rhs.has_value()) { - std::construct_at(std::addressof(this->val), *rhs); - } else { - std::construct_at(std::addressof(this->unex), rhs.error()); - } - } - - constexpr expected(expected&&) noexcept( - std::is_nothrow_move_constructible_v&& - std::is_nothrow_move_constructible_v) requires - std::move_constructible && std::move_constructible && - std::is_trivially_move_constructible_v && - std::is_trivially_move_constructible_v - = default; - - constexpr expected(expected&& rhs) noexcept( - std::is_nothrow_move_constructible_v&& - std::is_nothrow_move_constructible_v) requires - std::move_constructible && std::move_constructible - : has_val(rhs.has_value()) { - if (rhs.has_value()) { - std::construct_at(std::addressof(this->val), std::move(*rhs)); - } else { - std::construct_at(std::addressof(this->unex), std::move(rhs.error())); - } - } - - template - requires detail::expected_constructible_from_other - constexpr explicit(!std::convertible_to || - !std::convertible_to) - expected(expected const& rhs) // NOLINT - : has_val(rhs.has_value()) { - using UF = U const&; - using GF = G const&; - if (rhs.has_value()) { - std::construct_at(std::addressof(this->val), std::forward(*rhs)); - } else { - std::construct_at(std::addressof(this->unex), - std::forward(rhs.error())); - } - } - - template - requires detail::expected_constructible_from_other - constexpr explicit(!std::convertible_to || !std::convertible_to) - expected(expected&& rhs) // NOLINT - : has_val(rhs.has_value()) { - using UF = U const&; - using GF = G const&; - if (rhs.has_value()) { - std::construct_at(std::addressof(this->val), std::forward(*rhs)); - } else { - std::construct_at(std::addressof(this->unex), - std::forward(rhs.error())); - } - } - - template - requires(!std::same_as, std::in_place_t>) && - (!std::same_as, std::remove_cvref_t>)&& - (!detail::is_unexpected)&& - std::constructible_from - constexpr explicit(!std::convertible_to) expected(U&& v) // NOLINT - : val(std::forward(v)) {} - - template - requires std::constructible_from - constexpr explicit(!std::convertible_to) - expected(unexpected const& e) // NOLINT - : has_val{false}, unex(std::forward(e.value())) {} - - template - requires std::constructible_from - constexpr explicit(!std::convertible_to) - expected(unexpected&& e) // NOLINT - : has_val{false}, unex(std::forward(e.value())) {} - - template - requires std::constructible_from - constexpr explicit expected(std::in_place_t /*unused*/, Args&&... args) - : val(std::forward(args)...) {} - - template - requires std::constructible_from < T, std::initializer_list - &, Args... > constexpr explicit expected(std::in_place_t /*unused*/, - std::initializer_list il, - Args&&... args) - : val(il, std::forward(args)...) {} - - template - requires std::constructible_from - constexpr explicit expected(unexpect_t /*unused*/, Args&&... args) - : has_val{false}, unex(std::forward(args)...) {} - - template - requires std::constructible_from < E, std::initializer_list - &, - Args... > constexpr explicit expected(unexpect_t /*unused*/, - std::initializer_list il, - Args&&... args) - : has_val(false), - unex(il, std::forward(args)...) {} - - // destructor - constexpr ~expected() { - if constexpr (std::is_trivially_destructible_v and - std::is_trivially_destructible_v) { - } else if constexpr (std::is_trivially_destructible_v) { - if (!has_val) { - std::destroy_at(std::addressof(this->unex)); - } - } else if constexpr (std::is_trivially_destructible_v) { - if (has_val) { - std::destroy_at(std::addressof(this->val)); - } - } else { - if (has_val) { - std::destroy_at(std::addressof(this->val)); - } else { - std::destroy_at(std::addressof(this->unex)); - } - } - } - - // assignment - constexpr auto operator=(expected const& rhs) // NOLINT - -> expected& requires 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) - { - has_val = rhs.has_value(); - if (this->has_value() and rhs.has_value()) { - this->val = *rhs; - } else if (this->has_value()) { - detail::reinit_expected(this->unex, this->val, rhs.error()); - } else if (rhs.has_value()) { - detail::reinit_expected(this->val, this->unex, *rhs); - } else { - this->unex = rhs.error(); - } - return *this; - } - - constexpr auto operator=(expected&& rhs) // - noexcept(std::is_nothrow_move_assignable_v&& - std::is_nothrow_move_constructible_v&& - std::is_nothrow_move_assignable_v&& - std::is_nothrow_move_constructible_v) - -> expected& requires std::is_move_constructible_v && - std::is_move_assignable_v && - std::is_move_constructible_v && - std::is_move_assignable_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v) - { - has_val = rhs.has_value(); - if (this->has_value() and rhs.has_value()) { - this->val = std::move(*rhs); - } else if (this->has_value()) { - detail::reinit_expected(this->unex, this->val, std::move(rhs.error())); - } else if (rhs.has_value()) { - detail::reinit_expected(this->val, this->unex, std::move(*rhs)); - } else { - this->unex = std::move(rhs.error()); - } - return *this; - } - - template - constexpr auto operator=(U&& rhs) -> expected& - requires - (!std::same_as>) && - (!detail::is_unexpected>)&& - std::constructible_from&& - std::is_assignable_v && - ( std::is_nothrow_constructible_v || - std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v - ) { - if (this->has_value()) { - this->val = std::forward(rhs); - return *this; - } - detail::reinit_expected(this->val, this->unex, std::forward(rhs)); - has_val = true; - return *this; - } - - template - requires std::constructible_from && - std::is_assignable_v && - (std::is_nothrow_constructible_v || - std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v - ) - constexpr auto operator=(unexpected const& e) -> expected& { - using GF = G const&; - if (has_value()) { - detail::reinit_expected(this->unex, this->val, - std::forward(e.value())); - } else { - this->unex = std::forward(e.value()); - } - has_val = false; - return *this; - } - - template - requires std::constructible_from && - std::is_assignable_v && - (std::is_nothrow_constructible_v || - std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v - ) - constexpr auto operator=(unexpected&& e) -> expected& { - using GF = G; - if (has_value()) { - detail::reinit_expected(this->unex, this->val, - std::forward(e.value())); - } else { - this->unex = std::forward(e.value()); - } - has_val = false; - return *this; - } - - // modifiers - template - requires std::is_nothrow_constructible_v - constexpr auto emplace(Args&&... args) noexcept -> T& { - if (has_value()) { - std::destroy_at(std::addressof(this->val)); - } else { - std::destroy_at(std::addressof(this->unex)); - has_val = true; - } - return *std::construct_at(std::addressof(this->val), - std::forward(args)...); - } - - template - requires std::is_nothrow_constructible_v < T, std::initializer_list &, Args...> - constexpr auto emplace(std::initializer_list il, - Args&&... args) noexcept -> T& { - if (has_value()) { - std::destroy_at(std::addressof(this->val)); - } else { - std::destroy_at(std::addressof(this->unex)); - has_val = true; - } - return *std::construct_at(std::addressof(this->val), il, - std::forward(args)...); - } - - // swap - constexpr void swap(expected& rhs) noexcept( - std::is_nothrow_constructible_v&& - std::is_nothrow_swappable_v&& - std::is_nothrow_move_constructible_v&& - std::is_nothrow_swappable_v) - requires std::is_swappable_v && - std::is_swappable_v && - std::is_move_constructible_v && - std::is_move_constructible_v && - (std::is_nothrow_constructible_v || - std::is_nothrow_constructible_v) - { - if (rhs.has_value()) { - if (has_value()) { - using std::swap; - swap(this->val, rhs.val); - } else { - rhs.swap(*this); - } - } else { - if (has_value()) { - if constexpr (std::is_nothrow_move_constructible_v) { - E tmp(std::move(rhs.unex)); - std::destroy_at(std::addressof(rhs.unex)); - try { - std::construct_at(std::addressof(rhs.val), std::move(this->val)); - std::destroy_at(std::addressof(this->val)); - std::construct_at(std::addressof(this->unex), std::move(tmp)); - } catch (...) { - std::construct_at(std::addressof(rhs.unex), std::move(tmp)); - throw; - } - } else { - T tmp(std::move(this->val)); - std::destroy_at(std::addressof(this->val)); - try { - std::construct_at(std::addressof(this->unex), std::move(rhs.unex)); - std::destroy_at(std::addressof(rhs.unex)); - std::construct_at(std::addressof(rhs.val), std::move(tmp)); - } catch (...) { - std::construct_at(std::addressof(this->val), std::move(tmp)); - throw; - } - } - has_val = false; - rhs.has_val = true; - } else { - using std::swap; - swap(this->unex, rhs.unex); - } - } - } - - // observers - - // precondition: has_value() = true - constexpr auto operator->() const noexcept -> T const* { - return std::addressof(this->val); - } - - // precondition: has_value() = true - constexpr auto operator->() noexcept -> T* { - return std::addressof(this->val); - } - - // precondition: has_value() = true - constexpr auto operator*() const& noexcept -> T const& { return this->val; } - - // precondition: has_value() = true - constexpr auto operator*() & noexcept -> T& { return this->val; } - - // precondition: has_value() = true - constexpr auto operator*() const&& noexcept -> T const&& { - return std::move(this->val); - } - - // precondition: has_value() = true - constexpr auto operator*() && noexcept -> T&& { return std::move(this->val); } - - constexpr explicit operator bool() const noexcept { return has_val; } - - [[nodiscard]] constexpr auto has_value() const noexcept -> bool { - return has_val; - } - - constexpr auto value() const& -> T const& { - if (has_value()) { - return this->val; - } - throw bad_expected_access(error()); - } - - constexpr auto value() & -> T& { - if (has_value()) { - return this->val; - } - throw bad_expected_access(error()); - } - - constexpr auto value() const&& -> T const&& { - if (has_value()) { - return std::move(this->val); - } - throw bad_expected_access(std::move(error())); - } - - constexpr auto value() && -> T&& { - if (has_value()) { - return std::move(this->val); - } - throw bad_expected_access(std::move(error())); - } - - // precondition: has_value() = false - constexpr auto error() const& -> E const& { return this->unex; } - - // precondition: has_value() = false - constexpr auto error() & -> E& { return this->unex; } - - // precondition: has_value() = false - constexpr auto error() const&& -> E const&& { return std::move(this->unex); } - - // precondition: has_value() = false - constexpr auto error() && -> E&& { return std::move(this->unex); } - - template - requires - std::is_copy_constructible_v && std::is_convertible_v - constexpr auto value_or(U&& v) const& -> T { - return has_value() ? **this : static_cast(std::forward(v)); - } - - template - requires std::is_move_constructible_v && std::is_convertible_v - constexpr auto value_or(U&& v) && -> T { - return has_value() ? std::move(**this) : static_cast(std::forward(v)); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v && std::is_copy_constructible_v - constexpr auto and_then(F&& f) & { - if (has_value()) { - return std::invoke(std::forward(f), **this); - } - return U(unexpect, error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto and_then(F&& f) const& { - if (has_value()) { - return std::invoke(std::forward(f), **this); - } - return U(unexpect, error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto and_then(F&& f) && { - if (has_value()) { - return std::invoke(std::forward(f), std::move(**this)); - } - return U(unexpect, std::move(error())); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto and_then(F&& f) const&& { - if (has_value()) { - return std::invoke(std::forward(f), std::move(**this)); - } - return U(unexpect, std::move(error())); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) & { - if (has_value()) { - return G(**this); - } - return std::invoke(std::forward(f), error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) const& { - if (has_value()) { - return G(**this); - } - return std::invoke(std::forward(f), error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto or_else(F&& f) && { - if (has_value()) { - return G(std::move(**this)); - } - return std::invoke(std::forward(f), std::move(error())); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto or_else(F&& f) const&& { - if (has_value()) { - return G(std::move(**this)); - } - return std::invoke(std::forward(f), std::move(error())); - } - - template >> - requires std::is_void_v && - std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) & { - if (has_value()) { - return expected(*this); - } - std::invoke(std::forward(f), error()); - return expected(*this); - } - - template >> - requires std::is_void_v && - std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) const& { - if (has_value()) { - return expected(*this); - } - std::invoke(std::forward(f), error()); - return expected(*this); - } - - template >> - requires std::is_void_v && - std::is_move_constructible_v && - std::is_move_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) && { - if (has_value()) { - return expected(std::move(*this)); - } - // TODO: is this copy necessary, as f can be just read argument function - std::invoke(std::forward(f), error()); - return expected(std::move(*this)); - } - - template >> - requires std::is_void_v && - std::is_move_constructible_v && - std::is_move_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) const&& { - if (!has_value()) { - return expected(std::move(*this)); - } - // TODO: is this copy necessary, as f can be just read argument function - std::invoke(std::forward(f), error()); - return expected(std::move(*this)); - } - - template >> - requires std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto transform(F&& f) & { - if (has_value()) { - if constexpr (!std::same_as) { - return expected(std::invoke(std::forward(f), **this)); - } else { - std::invoke(std::forward(f), std::move(**this)); - return expected(); - } - } - return expected(unexpect, error()); - } - - template >> - requires std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto transform(F&& f) const& { - if (has_value()) { - if constexpr (!std::same_as) { - return expected(std::invoke(std::forward(f), **this)); - } else { - std::invoke(std::forward(f), std::move(**this)); - return expected(); - } - } - return expected(unexpect, error()); - } - - template >> - requires std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto transform(F&& f) && { - if (has_value()) { - if constexpr (!std::same_as) { - return expected( - std::invoke(std::forward(f), std::move(**this))); - } else { - std::invoke(std::forward(f), std::move(**this)); - return expected(); - } - } - return expected(unexpect, std::move(error())); - } - - template >> - requires std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto transform(F&& f) const&& { - if (has_value()) { - if constexpr (!std::same_as) { - return expected( - std::invoke(std::forward(f), std::move(**this))); - } else { - std::invoke(std::forward(f), std::move(**this)); - return expected(); - } - } - return expected(unexpect, std::move(error())); - } - - template >> - requires std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto transform_error(F&& f) & { - if (has_value()) { - return expected(**this); - } - return expected(unexpect, std::invoke(std::forward(f), error())); - } - - template >> - requires std::is_copy_constructible_v && - std::is_copy_constructible_v - constexpr auto transform_error(F&& f) const& { - if (has_value()) { - return expected(**this); - } - return expected(unexpect, std::invoke(std::forward(f), error())); - } - - template >> - requires std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto transform_error(F&& f) && { - if (has_value()) { - return expected(std::move(**this)); - } - return expected(unexpect, - std::invoke(std::forward(f), std::move(error()))); - } - - template >> - requires std::is_move_constructible_v && - std::is_move_constructible_v - constexpr auto transform_error(F&& f) const&& { - if (has_value()) { - return expected(std::move(**this)); - } - return expected(unexpect, - std::invoke(std::forward(f), std::move(error()))); - } - - // equality operators - template - requires(!std::is_void_v) && - requires(T const& t1, T2 const& t2, E const& e1, E2 const& e2) { - { t1 == t2 } -> std::convertible_to; - { e1 == e2 } -> std::convertible_to; - } - friend constexpr auto operator==(expected const& x, expected const& y) - -> bool { - if (x.has_value() != y.has_value()) { - return false; - } - return x.has_value() ? (*x == *y) : (x.error() == y.error()); - } - - template - requires(!detail::is_expected) && requires(T const& x, T2 const& v) { - { x == v } -> std::convertible_to; - } - friend constexpr auto operator==(expected const& x, T2 const& v) -> bool { - return x.has_value() && static_cast(*x == v); - } - - template - requires requires(E const& x, unexpected const& e) { - { x == e.value() } -> std::convertible_to; - } - friend constexpr auto operator==(expected const& x, unexpected const& e) - -> bool { - return !x.has_value() && static_cast(x.error() == e.value()); - } - - // specialized algorithms - friend constexpr void swap(expected& x, - expected& y) noexcept(noexcept(x.swap(y))) { - x.swap(y); - } - - private: - bool has_val{true}; - union { - T val; - E unex; - }; -}; - -template -class expected { - public: - using value_type = void; - using error_type = E; - using unexpected_type = unexpected; - - template - using rebind = expected; - - // constructors - - // postcondition: has_value() = true - constexpr expected() noexcept {} // NOLINT - - constexpr expected( - expected const& rhs) requires std::is_copy_constructible_v && - std::is_trivially_copy_constructible_v - = default; - - constexpr expected( - expected const& rhs) requires std::is_copy_constructible_v - : has_val(rhs.has_value()) { - if (!rhs.has_value()) { - std::construct_at(std::addressof(this->unex), rhs.error()); - } - } - - constexpr expected(expected&&) - noexcept(std::is_nothrow_move_constructible_v) - requires std::is_move_constructible_v && - std::is_trivially_move_constructible_v - = default; - - constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v) - requires std::is_move_constructible_v : has_val(rhs.has_value()) { - if (!rhs.has_value()) { - std::construct_at(std::addressof(this->unex), std::move(rhs.error())); - } - } - - template - requires std::is_void_v - && std::is_constructible_v - &&(!std::is_constructible_v, expected&>) - &&(!std::is_constructible_v, expected>) - &&(!std::is_constructible_v, expected const&>) - &&(!std::is_constructible_v, expected const&>) - constexpr explicit(!std::is_convertible_v) - expected(expected const& rhs) // NOLINT - : has_val(rhs.has_value()) { - if (!rhs.has_value()) { - std::construct_at(std::addressof(this->unex), - std::forward(rhs.error())); - } - } - - template - requires std::is_void_v - && std::is_constructible_v - &&(!std::is_constructible_v, expected&>) - &&(!std::is_constructible_v, expected>) - &&(!std::is_constructible_v, expected const&>) - &&(!std::is_constructible_v, expected const&>) - constexpr explicit(!std::is_convertible_v) - expected(expected&& rhs) // NOLINT - : has_val(rhs.has_value()) { - if (!rhs.has_value()) { - std::construct_at(std::addressof(this->unex), - std::forward(rhs.error())); - } - } - - template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) - expected(unexpected const& e) // NOLINT - : has_val(false), unex(std::forward(e.value())) {} - - template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) - expected(unexpected&& e) // NOLINT - : has_val(false), unex(std::forward(e.value())) {} - - constexpr explicit expected(std::in_place_t /*unused*/) noexcept {} - - template - requires std::is_constructible_v - constexpr explicit expected(unexpect_t /*unused*/, Args&&... args) - : has_val(false), unex(std::forward(args)...) {} - - template - requires std::is_constructible_v &, Args...> - constexpr explicit expected(unexpect_t /*unused*/, std::initializer_list il, Args... args) - : has_val(false), - unex(il, std::forward(args)...) {} - - // destructor - constexpr ~expected() { - if constexpr (std::is_trivially_destructible_v) { - } else { - if (!has_value()) std::destroy_at(std::addressof(this->unex)); - } - } - - // assignment - constexpr auto operator=(expected const& rhs) -> expected& // NOLINT - requires std::is_copy_assignable_v && - std::is_copy_constructible_v { - if (has_value() && rhs.has_value()) { - } else if (has_value()) { - std::construct_at(std::addressof(this->unex), rhs.unex); - has_val = false; - } else if (rhs.has_value()) { - std::destroy_at(std::addressof(this->unex)); - has_val = true; - } else { - this->unex = rhs.error(); - } - return *this; - } - - constexpr auto operator=(expected&& rhs) - noexcept(std::is_nothrow_move_constructible_v&& - std::is_nothrow_move_assignable_v) -> expected& - requires std::is_move_constructible_v && - std::is_move_assignable_v { - if (has_value() && rhs.has_value()) { - } else if (has_value()) { - std::construct_at(std::addressof(this->unex), std::move(rhs.unex)); - has_val = false; - } else if (rhs.has_value()) { - std::destroy_at(std::addressof(this->unex)); - has_val = true; - } else { - this->unex = std::move(rhs.error()); - } - return *this; - } - - template - requires std::is_constructible_v and - std::is_assignable_v - constexpr auto operator=(unexpected const& e) -> expected& { - if (has_value()) { - std::construct_at(std::addressof(this->unex), - std::forward(e.value())); - has_val = false; - } else { - this->unex = std::forward(e.value()); - } - return *this; - } - - template - requires std::is_constructible_v && - std::is_assignable_v - constexpr auto operator=(unexpected&& e) -> expected& { - if (has_value()) { - std::construct_at(std::addressof(this->unex), std::forward(e.value())); - has_val = false; - } else { - this->unex = std::forward(e.value()); - } - return *this; - } - - // modifiers - constexpr void emplace() noexcept { - if (!has_value()) { - std::destroy_at(std::addressof(this->unex)); - has_val = true; - } - } - - // swap - constexpr void swap(expected& rhs) - noexcept(std::is_nothrow_move_constructible_v&& - std::is_nothrow_swappable_v) - requires std::is_swappable_v && - std::is_move_constructible_v - { - if (rhs.has_value()) { - if (has_value()) { - } else { - rhs.swap(*this); - } - } else { - if (has_value()) { - std::construct_at(std::addressof(this->unex), std::move(rhs.unex)); - std::destroy_at(std::addressof(rhs.unex)); - has_val = false; - rhs.has_val = true; - } else { - using std::swap; - swap(this->unex, rhs.unex); - } - } - } - - // observers - constexpr explicit operator bool() const noexcept { return has_val; } - - [[nodiscard]] constexpr auto has_value() const noexcept -> bool { - return has_val; - } - - // precondition: has_value() = true - constexpr void operator*() const noexcept {} - - constexpr void value() const& { - if (!has_value()) { - throw bad_expected_access(error()); - } - } - - constexpr void value() && { - if (!has_value()) { - throw bad_expected_access(std::move(error())); - } - } - - // precondition: has_value() = false - constexpr auto error() const& -> E const& { return this->unex; } - - // precondition: has_value() = false - constexpr auto error() & -> E& { return this->unex; } - - // precondition: has_value() = false - constexpr auto error() const&& -> E const&& { return std::move(this->unex); } - - // precondition: has_value() = false - constexpr auto error() && -> E&& { return std::move(this->unex); } - - // monadic - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v - constexpr auto and_then(F&& f) & { - if (has_value()) { - return std::invoke(std::forward(f)); - } - return U(unexpect, error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v - constexpr auto and_then(F&& f) const& { - if (has_value()) { - return std::invoke(std::forward(f)); - } - return U(unexpect, error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v - constexpr auto and_then(F&& f) && { - if (has_value()) { - return std::invoke(std::forward(f)); - } - return U(unexpect, std::move(error())); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v - constexpr auto and_then(F&& f) const&& { - if (has_value()) { - return std::invoke(std::forward(f)); - } - return U(unexpect, std::move(error())); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) & { - if (has_value()) { - return G{}; - } - return std::invoke(std::forward(f), error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) const& { - if (has_value()) { - return G{}; - } - return std::invoke(std::forward(f), error()); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v - constexpr auto or_else(F&& f) && { - if (has_value()) { - return G{}; - } - return std::invoke(std::forward(f), std::move(error())); - } - - template >> - requires detail::is_expected && - std::is_same_v && - std::is_move_constructible_v - constexpr auto or_else(F&& f) const&& { - if (has_value()) { - return G{}; - } - return std::invoke(std::forward(f), std::move(error())); - } - - template >> - requires std::is_void_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) & { - if (has_value()) { - return expected(*this); - } - std::invoke(std::forward(f), error()); - return expected(*this); - } - - template >> - requires std::is_void_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) const& { - if (has_value()) { - return expected(*this); - } - std::invoke(std::forward(f), error()); - return expected(*this); - } - - template >> - requires std::is_void_v && - std::is_move_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) && { - if (has_value()) { - return expected(std::move(*this)); - } - // TODO: is this copy necessary, as f can be just read argument function - std::invoke(std::forward(f), error()); - return expected(std::move(*this)); - } - - template >> - requires std::is_void_v && - std::is_move_constructible_v && - std::is_copy_constructible_v - constexpr auto or_else(F&& f) const&& { - if (!has_value()) { - return expected(std::move(*this)); - } - // TODO: is this copy necessary, as f can be just read argument function - std::invoke(std::forward(f), error()); - return expected(std::move(*this)); - } - - template >> - requires std::is_copy_constructible_v - constexpr auto transform(F&& f) & { - if (has_value()) { - if constexpr (!std::same_as) { - return expected(std::invoke(std::forward(f))); - } else { - std::invoke(std::forward(f)); - return expected(); - } - } - return expected(unexpect, error()); - } - - template >> - requires std::is_copy_constructible_v - constexpr auto transform(F&& f) const& { - if (has_value()) { - if constexpr (!std::same_as) { - return expected(std::invoke(std::forward(f))); - } else { - std::invoke(std::forward(f)); - return expected(); - } - } - return expected(unexpect, error()); - } - - template >> - requires std::is_move_constructible_v - constexpr auto transform(F&& f) && { - if (has_value()) { - if constexpr (!std::same_as) { - return expected(std::invoke(std::forward(f))); - } else { - std::invoke(std::forward(f)); - return expected(); - } - } - return expected(unexpect, std::move(error())); - } - - template >> - requires std::is_move_constructible_v - constexpr auto transform(F&& f) const&& { - if (has_value()) { - if constexpr (!std::same_as) { - return expected(std::invoke(std::forward(f))); - } else { - std::invoke(std::forward(f)); - return expected(); - } - } - return expected(unexpect, std::move(error())); - } - - template >> - requires std::is_copy_constructible_v - constexpr auto transform_error(F&& f) & { - if (has_value()) { - return expected{}; - } - return expected(unexpect, - std::invoke(std::forward(f), error())); - } - - template >> - requires std::is_copy_constructible_v - constexpr auto transform_error(F&& f) const& { - if (has_value()) { - return expected{}; - } - return expected(unexpect, - std::invoke(std::forward(f), error())); - } - - template >> - requires std::is_move_constructible_v - constexpr auto transform_error(F&& f) && { - if (has_value()) { - return expected{}; - } - return expected( - unexpect, std::invoke(std::forward(f), std::move(error()))); - } - - template >> - requires std::is_move_constructible_v - constexpr auto transform_error(F&& f) const&& { - if (has_value()) { - return expected{}; - } - return expected( - unexpect, std::invoke(std::forward(f), std::move(error()))); - } - - // expected equality operators - template - requires std::is_void_v && requires(E e, E2 e2) { - { e == e2 } -> std::convertible_to; - } - friend constexpr auto operator==(expected const& x, expected const& y) - -> bool { - if (x.has_value() != y.has_value()) return false; - return x.has_value() or static_cast(x.error() == y.error()); - } - - template - requires requires(expected const& x, unexpected const& e) { - { x.error() == e.value() } -> std::convertible_to; - } - friend constexpr auto operator==(expected const& x, unexpected const& e) - -> bool { - return !x.has_value() && static_cast(x.error() == e.value()); - } - - // specialized algorithms - friend constexpr void swap(expected& x, - expected& y) noexcept(noexcept(x.swap(y))) { - x.swap(y); - } - - private: - bool has_val{true}; - union { - E unex; - }; -}; - -// NOLINTEND(*) -// clang-format on - -} // namespace std23 diff --git a/cpp/mrc/include/mrc/coroutines/ring_buffer.hpp b/cpp/mrc/include/mrc/coroutines/ring_buffer.hpp index 94625ff4b..de4b8b3d9 100644 --- a/cpp/mrc/include/mrc/coroutines/ring_buffer.hpp +++ b/cpp/mrc/include/mrc/coroutines/ring_buffer.hpp @@ -38,7 +38,7 @@ #pragma once -#include "mrc/core/std23_expected.hpp" +#include "mrc/core/expected.hpp" #include "mrc/coroutines/schedule_policy.hpp" #include "mrc/coroutines/thread_local_context.hpp" #include "mrc/coroutines/thread_pool.hpp" @@ -243,13 +243,13 @@ class RingBuffer /** * @return The consumed element or std::nullopt if the read has failed. */ - auto await_resume() -> std23::expected + auto await_resume() -> mrc::expected { ThreadLocalContext::resume_thread_local_context(); if (m_stopped) { - return std23::unexpected(RingBufferOpStatus::Stopped); + return mrc::unexpected(RingBufferOpStatus::Stopped); } return std::move(m_e); diff --git a/cpp/mrc/src/internal/control_plane/client.hpp b/cpp/mrc/src/internal/control_plane/client.hpp index 16b8db778..b9a211dbc 100644 --- a/cpp/mrc/src/internal/control_plane/client.hpp +++ b/cpp/mrc/src/internal/control_plane/client.hpp @@ -23,7 +23,7 @@ #include "internal/resources/partition_resources_base.hpp" #include "internal/service.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/node/source_channel.hpp" #include "mrc/protos/architect.grpc.pb.h" #include "mrc/protos/architect.pb.h" diff --git a/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp b/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp index 25f05ec2a..3dd97a11d 100644 --- a/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp +++ b/cpp/mrc/src/internal/control_plane/client/connections_manager.cpp @@ -24,7 +24,7 @@ #include "internal/ucx/worker.hpp" #include "internal/utils/contains.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/core/task_queue.hpp" #include "mrc/protos/architect.pb.h" diff --git a/cpp/mrc/src/internal/control_plane/client/state_manager.cpp b/cpp/mrc/src/internal/control_plane/client/state_manager.cpp index 4b6b4083a..5130f8f0b 100644 --- a/cpp/mrc/src/internal/control_plane/client/state_manager.cpp +++ b/cpp/mrc/src/internal/control_plane/client/state_manager.cpp @@ -20,7 +20,7 @@ #include "internal/control_plane/client.hpp" #include "internal/runnable/resources.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/node/edge_builder.hpp" #include "mrc/node/rx_sink.hpp" #include "mrc/node/source_channel.hpp" diff --git a/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp b/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp index a34818b31..6defbd462 100644 --- a/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp +++ b/cpp/mrc/src/internal/control_plane/client/subscription_service.cpp @@ -22,7 +22,7 @@ #include "internal/service.hpp" #include "internal/utils/contains.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/protos/architect.pb.h" #include diff --git a/cpp/mrc/src/internal/control_plane/proto_helpers.hpp b/cpp/mrc/src/internal/control_plane/proto_helpers.hpp index 04421829a..d0c72d8c8 100644 --- a/cpp/mrc/src/internal/control_plane/proto_helpers.hpp +++ b/cpp/mrc/src/internal/control_plane/proto_helpers.hpp @@ -17,7 +17,7 @@ #pragma once -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include diff --git a/cpp/mrc/src/internal/control_plane/server.cpp b/cpp/mrc/src/internal/control_plane/server.cpp index ecc0e3fd9..0cb37058f 100644 --- a/cpp/mrc/src/internal/control_plane/server.cpp +++ b/cpp/mrc/src/internal/control_plane/server.cpp @@ -309,11 +309,11 @@ void Server::do_handle_event(event_t&& event) DVLOG(10) << "event.ok failed; close stream"; drop_stream(event.stream); } - } catch (const std23::bad_expected_access& e) + } catch (const mrc::bad_expected_access& e) { LOG(ERROR) << "bad_expected_access: " << e.error().message(); on_fatal_exception(); - } catch (const UnexpectedError& e) + } catch (const mrc::unexpected& e) { LOG(ERROR) << "unexpected: " << e.value().message(); on_fatal_exception(); diff --git a/cpp/mrc/src/internal/control_plane/server.hpp b/cpp/mrc/src/internal/control_plane/server.hpp index b873be9ee..263c5dffe 100644 --- a/cpp/mrc/src/internal/control_plane/server.hpp +++ b/cpp/mrc/src/internal/control_plane/server.hpp @@ -22,7 +22,7 @@ #include "internal/grpc/server_streaming.hpp" #include "internal/service.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/node/queue.hpp" #include "mrc/protos/architect.grpc.pb.h" diff --git a/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp b/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp index 74cc60edf..8add0553a 100644 --- a/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp +++ b/cpp/mrc/src/internal/control_plane/server/connection_manager.hpp @@ -20,7 +20,7 @@ #include "internal/control_plane/server/versioned_issuer.hpp" #include "internal/grpc/server_streaming.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/types.hpp" #include diff --git a/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp b/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp index e68e4f7d5..d1f0c6eae 100644 --- a/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp +++ b/cpp/mrc/src/internal/control_plane/server/subscription_manager.hpp @@ -20,7 +20,7 @@ #include "internal/control_plane/server/tagged_issuer.hpp" #include "internal/control_plane/server/versioned_issuer.hpp" -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include "mrc/types.hpp" #include diff --git a/cpp/mrc/src/tests/test_expected.cpp b/cpp/mrc/src/tests/test_expected.cpp index 8a4eb666d..663fbb0d0 100644 --- a/cpp/mrc/src/tests/test_expected.cpp +++ b/cpp/mrc/src/tests/test_expected.cpp @@ -15,7 +15,7 @@ * limitations under the License. */ -#include "mrc/core/expected.hpp" +#include "mrc/core/error.hpp" #include #include From aa508dfb20061325abb4234f6c01fa207bdfb6fe Mon Sep 17 00:00:00 2001 From: Ryan Olson Date: Tue, 27 Dec 2022 07:14:18 +0000 Subject: [PATCH 4/4] fixing ll bug; adding latch to tests; adding benchmark --- cpp/mrc/benchmarks/bench_coroutines.cpp | 48 +++++++++++++++++++ .../mrc/channel/v2/immediate_channel.hpp | 8 ++-- .../tests/channels/test_immediate_channel.cpp | 38 +++++++++++---- 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/cpp/mrc/benchmarks/bench_coroutines.cpp b/cpp/mrc/benchmarks/bench_coroutines.cpp index 82a2aa437..ead732a2f 100644 --- a/cpp/mrc/benchmarks/bench_coroutines.cpp +++ b/cpp/mrc/benchmarks/bench_coroutines.cpp @@ -15,8 +15,10 @@ * limitations under the License. */ +#include "mrc/channel/v2/immediate_channel.hpp" #include "mrc/coroutines/sync_wait.hpp" #include "mrc/coroutines/task.hpp" +#include "mrc/coroutines/when_all.hpp" #include @@ -130,9 +132,55 @@ static void mrc_coro_await_incrementing_awaitable_baseline(benchmark::State& sta coroutines::sync_wait(task()); } +static void mrc_coro_immediate_channel(benchmark::State& state) +{ + channel::v2::ImmediateChannel immediate_channel; + + auto src = [&]() -> coroutines::Task<> { + for (auto _ : state) + { + co_await immediate_channel.async_write(42); + } + immediate_channel.close(); + co_return; + }; + + auto sink = [&]() -> coroutines::Task<> { + while (auto val = co_await immediate_channel.async_read()) {} + co_return; + }; + + coroutines::sync_wait(coroutines::when_all(sink(), src())); +} + +static auto bar(std::size_t i) -> std::size_t +{ + return i += 5; +} + +static void foo(std::size_t i) +{ + benchmark::DoNotOptimize(bar(i)); +} + +static void mrc_coro_immedate_channel_composite_fn_baseline(benchmark::State& state) +{ + auto task = [&]() -> coroutines::Task<> { + for (auto _ : state) + { + foo(42); + } + co_return; + }; + + coroutines::sync_wait(task()); +} + BENCHMARK(mrc_coro_create_single_task_and_sync); BENCHMARK(mrc_coro_create_single_task_and_sync_on_when_all); BENCHMARK(mrc_coro_create_two_tasks_and_sync_on_when_all); BENCHMARK(mrc_coro_await_suspend_never); BENCHMARK(mrc_coro_await_incrementing_awaitable_baseline); BENCHMARK(mrc_coro_await_incrementing_awaitable); +BENCHMARK(mrc_coro_immediate_channel); +BENCHMARK(mrc_coro_immedate_channel_composite_fn_baseline); diff --git a/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp b/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp index 0b17d0b8e..c980a680f 100644 --- a/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp +++ b/cpp/mrc/include/mrc/channel/v2/immediate_channel.hpp @@ -18,8 +18,8 @@ #pragma once #include "mrc/channel/status.hpp" +#include "mrc/core/error.hpp" #include "mrc/core/expected.hpp" -#include "mrc/core/std23_expected.hpp" #include @@ -79,7 +79,7 @@ class ImmediateChannel else { // put current writer at the end of the fifo writer resumer list - auto* write_resumer = m_parent.m_write_resumers->m_next; + auto* write_resumer = m_parent.m_write_resumers; while (write_resumer->m_next != nullptr) { write_resumer = write_resumer->m_next; @@ -128,11 +128,11 @@ class ImmediateChannel m_parent.m_read_waiters = this; } - auto await_resume() noexcept -> std23::expected + auto await_resume() noexcept -> mrc::expected { if (m_channel_closed) [[unlikely]] { - return std23::unexpected(Status::closed); + return mrc::unexpected(Status::closed); } return {std::move(m_data)}; diff --git a/cpp/mrc/tests/channels/test_immediate_channel.cpp b/cpp/mrc/tests/channels/test_immediate_channel.cpp index 3681b2e63..88bfd7dcf 100644 --- a/cpp/mrc/tests/channels/test_immediate_channel.cpp +++ b/cpp/mrc/tests/channels/test_immediate_channel.cpp @@ -16,6 +16,7 @@ */ #include "mrc/channel/v2/immediate_channel.hpp" +#include "mrc/coroutines/latch.hpp" #include "mrc/coroutines/sync_wait.hpp" #include "mrc/coroutines/task.hpp" #include "mrc/coroutines/when_all.hpp" @@ -36,12 +37,19 @@ class TestChannelV2 : public ::testing::Test ImmediateChannel m_channel; - coroutines::Task int_writer(int iterations) + coroutines::Task int_writer(int iterations, coroutines::Latch& latch) { for (int i = 0; i < iterations; i++) { co_await m_channel.async_write(std::move(i)); } + latch.count_down(); + co_return; + } + + coroutines::Task<> close_on_latch(coroutines::Latch& latch) + { + co_await latch; m_channel.close(); co_return; } @@ -86,38 +94,48 @@ TEST_F(TestChannelV2, ChannelClosed) TEST_F(TestChannelV2, SingleWriterSingleReader) { - coroutines::sync_wait(coroutines::when_all(int_writer(3), int_reader(3))); + coroutines::Latch latch{1}; + coroutines::sync_wait(coroutines::when_all(close_on_latch(latch), int_writer(3, latch), int_reader(3))); } TEST_F(TestChannelV2, Readerx1_Writer_x1) { - coroutines::sync_wait(coroutines::when_all(int_reader(3), int_writer(3))); + coroutines::Latch latch{1}; + coroutines::sync_wait(coroutines::when_all(int_reader(3), int_writer(3, latch), close_on_latch(latch))); } TEST_F(TestChannelV2, Readerx2_Writer_x1) { - coroutines::sync_wait(coroutines::when_all(int_reader(2), int_reader(1), int_writer(3))); + coroutines::Latch latch{1}; + coroutines::sync_wait( + coroutines::when_all(int_reader(2), int_reader(1), int_writer(3, latch), close_on_latch(latch))); } TEST_F(TestChannelV2, Readerx3_Writer_x1) { - coroutines::sync_wait(coroutines::when_all(int_reader(1), int_reader(1), int_reader(1), int_writer(3))); + coroutines::Latch latch{1}; + coroutines::sync_wait( + coroutines::when_all(close_on_latch(latch), int_reader(1), int_reader(1), int_reader(1), int_writer(3, latch))); } TEST_F(TestChannelV2, Readerx4_Writer_x1) { // reader are a lifo, so the first reader in the task list will not get a data entry - coroutines::sync_wait( - coroutines::when_all(int_reader(0), int_reader(1), int_reader(1), int_reader(1), int_writer(3))); + coroutines::Latch latch{1}; + coroutines::sync_wait(coroutines::when_all( + close_on_latch(latch), int_reader(0), int_reader(1), int_reader(1), int_reader(1), int_writer(3, latch))); } TEST_F(TestChannelV2, Readerx3_Writer_x1_Reader_x1) { - coroutines::sync_wait( - coroutines::when_all(int_reader(1), int_reader(1), int_reader(1), int_writer(3), int_reader(0))); + coroutines::Latch latch{1}; + coroutines::sync_wait(coroutines::when_all( + int_reader(1), int_reader(1), close_on_latch(latch), int_reader(1), int_writer(3, latch), int_reader(0))); } TEST_F(TestChannelV2, Writer_2_Reader_x2) { - coroutines::sync_wait(coroutines::when_all(int_writer(2), int_writer(2), int_reader(4), int_reader(0))); + coroutines::Latch latch{1}; + coroutines::sync_wait(coroutines::when_all( + int_writer(2, latch), int_writer(2, latch), close_on_latch(latch), int_reader(4), int_reader(0))); }