diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d311b2..c27cf9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.27 FATAL_ERROR) -project(termite VERSION 0.0.1 LANGUAGES CXX) +project(termite VERSION 0.0.2 LANGUAGES CXX) include(CMakeDependentOption) include(CTest) @@ -80,15 +80,21 @@ if(TERMITE_BUILD_PACKAGE) install(DIRECTORY "include/" TYPE INCLUDE) - list(APPEND sourceGenerators TBZ2 TGZ TXZ ZIP) - list(APPEND binaryGenerators "DEB") + list(APPEND sourceGenerators "TGZ" "ZIP") + list(APPEND binaryGenerators "DEB" "RPM") set(CPACK_SOURCE_GENERATOR ${sourceGenerators}) set(CPACK_GENERATOR ${binaryGenerators}) set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${PROJECT_VERSION}") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "obsidian") - list(APPEND CPACK_SOURCE_IGNORE_FILES /.git/ /build/ .gitignore) + list(APPEND CPACK_SOURCE_IGNORE_FILES + /.github/ + /.git/ + /build/ + /.cache/ + .gitignore + ) include(CPack) endif() diff --git a/README.md b/README.md index 82fbf6e..898cae8 100644 --- a/README.md +++ b/README.md @@ -2,36 +2,13 @@ Single header implementation of `std::expected` written in c++20. -According to the standard document, -[`std::expected`](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf) -is a vocabulary type which contains an expected value of type `T` , or an error `E`. -The class skews towards behaving like a `T`, because its intended use is when the -expected type is contained. -When something unexpected occurs, more typing is required. When all is good, code -mostly looks as if a `T` were being handled. -This implementation provides a number of utilities to make coding with -`Expected Object` cleaner. +According to the standard document, `std::expected` is a vocabulary type which +contains an expected value of type `T` , or an error `E`. The class skews towards behaving +like a `T`, because its intended use is when the expected type is contained. +When something unexpected occurs, more typing is required. When all is good, +code mostly looks as if a `T` were being handled. This implementation provides +a number of utilities to make coding with `Expected Object` cleaner. -Calling the `Error()` method on an expected value, -or using the `*` or `->` operators on an unexpected value, is undefined behavior. -In this implementation it causes an assertion failure. -The implementation of assertions can be overridden by defining the macro -`TERMITE_ASSERT(boolean_condition)` before #including ; -by default, the assertion causes an unreachable failure, which uses -compiler specific extensions if possible, otherwise, does nothing. -Note that correct code would not rely on these assertions. +Calling the `Error()` method on an expected value, or using the `*` or `->` operators on an unexpected value, is undefined behavior. In this implementation it causes an assertion failure. The implementation of assertions can be overridden by defining the macro `TERMITE_ASSERT(boolean_condition)` before #including ; by default, the assertion causes an unreachable failure, which uses compiler specific extensions if possible, otherwise, does nothing. Note that correct code would not rely on these assertions. -In the implementation, -the difference between calling the `Value()` method and using the `*` operator -on an unexpected value is only that one will throw a exception, another will -cause a assertion failure. -Considering people coding in a range of environment, -every environment has its own requirement. -In some scene which the size of binary file should be considered, -you had better disable exception handling to get smaller size. -In order to achieve the arch-independent goal, -the implementation delete the `Value()` method, and users should using -the `*` operator to get the expected value directly. -If you wish to use the `Value()` method no matter how the exception handling -performs worse, you can report an issue to this library and the developers would -like to adopt your opinion. +In the implementation, the difference between calling the `Value()` method and using the `*` operator on an unexpected value is only that one will throw a exception, another will cause a assertion failure. Considering people coding in a range of environment, every environment has its own requirement. In some scene which the size of binary file should be considered, you had better disable exception handling to get smaller size. In order to achieve the arch-independent goal, the implementation delete the `Value()` method, and users should using the `*` operator to get the expected value directly. If you wish to use the `Value()` method no matter how the exception handling performs worse, you can report an issue to this library and the developers would like to adopt your opinion. diff --git a/include/termite/expected.h b/include/termite/expected.h index 976c4ed..8262a1f 100644 --- a/include/termite/expected.h +++ b/include/termite/expected.h @@ -9,17 +9,17 @@ #ifndef TERMITE_ASSERT #ifdef __GNUC__ -#define TERMITE_UNREACHABLE (__builtin_unreachable() ) +#define TERMITE_UNREACHABLE (__builtin_unreachable()) #elif defined(_MSC_VER) -#define TERMITE_UNREACHABLE (__assume(false) ) +#define TERMITE_UNREACHABLE (__assume(false)) #else #define TERMITE_UNREACHABLE ((void)0) #endif -#define TERMITE_ASSERT(cond) \ +#define TERMITE_ASSERT(cond) \ do { \ if (std::is_constant_evaluated() && !static_cast(cond)) { \ - TERMITE_UNREACHABLE; \ + TERMITE_UNREACHABLE; \ } \ } while (false) #endif @@ -947,7 +947,7 @@ class Expected { (!std::is_trivially_copy_constructible_v) : has_val_(rhs.HasValue()) { if (!rhs.HasValue()) { - std::construct_at(std::addressof(unex_), rhs.Error()); + std::construct_at(std::addressof(Unex()), rhs.Error()); } } @@ -962,7 +962,7 @@ class Expected { (!std::is_trivially_move_constructible_v) : has_val_(rhs.HasValue()) { if (!rhs.HasValue()) { - std::construct_at(std::addressof(unex_), std::move(rhs.Error())); + std::construct_at(std::addressof(Unex()), std::move(rhs.Error())); } } @@ -979,7 +979,7 @@ class Expected { Expected(const Expected& rhs) : has_val_(rhs.HasValue()) { if (!rhs.HasValue()) { - std::construct_at(std::addressof(unex_), + std::construct_at(std::addressof(Unex()), std::forward(rhs.Error())); } } @@ -991,7 +991,7 @@ class Expected { Expected(Expected&& rhs) : has_val_(rhs.HasValue()) { if (!rhs.HasValue()) { - std::construct_at(std::addressof(unex_), std::forward(rhs.Error())); + std::construct_at(std::addressof(Unex()), std::forward(rhs.Error())); } } @@ -999,29 +999,46 @@ class Expected { requires std::is_constructible_v constexpr explicit(!std::is_convertible_v) Expected(const Unexpected& e) - : unex_(std::forward(e.Error())), has_val_(false) {} + : has_val_(false) { + if (!HasValue()) { + std::construct_at(std::addressof(Unex()), + std::forward(e.Error())); + } + } template requires std::is_constructible_v constexpr explicit(!std::is_convertible_v) Expected(Unexpected&& e) - : unex_(std::forward(e.Error())), has_val_(false) {} + : has_val_(false) { + if (!HasValue()) { + std::construct_at(std::addressof(Unex()), std::forward(e.Error())); + } + } constexpr explicit Expected(InPlaceTag) noexcept : has_val_(true) {} template requires std::is_constructible_v - constexpr explicit Expected(UnexpectTag, Args&&... args) - : unex_(std::forward(args)...), has_val_(false) {} + constexpr explicit Expected(UnexpectTag, Args&&... args) : has_val_(false) { + if (!HasValue()) { + std::construct_at(std::addressof(Unex()), std::forward(args)...); + } + } template requires std::is_constructible_v&, Args...> constexpr explicit Expected(UnexpectTag, std::initializer_list il, Args&&... args) - : unex_(il, std::forward(args)...), has_val_(false) {} + : has_val_(false) { + if (!HasValue()) { + std::construct_at(std::addressof(Unex()), il, + std::forward(args)...); + } + } constexpr ~Expected() { if (!HasValue()) { - std::destroy_at(std::addressof(unex_)); + std::destroy_at(std::addressof(Unex())); } } @@ -1040,13 +1057,13 @@ class Expected { if (HasValue() && rhs.HasValue()) { } else if (HasValue()) { - std::construct_at(std::addressof(unex_), rhs.Error()); + std::construct_at(std::addressof(Unex()), rhs.Error()); has_val_ = false; } else if (rhs.HasValue()) { - std::destroy_at(std::addressof(unex_)); + std::destroy_at(std::addressof(Unex())); has_val_ = true; } else { - unex_ = rhs.Error(); + Unex() = rhs.Error(); } return *this; } @@ -1062,13 +1079,13 @@ class Expected { if (HasValue() && rhs.HasValue()) { } else if (HasValue()) { - std::construct_at(std::addressof(unex_), std::move(rhs.Error())); + std::construct_at(std::addressof(Unex()), std::move(rhs.Error())); has_val_ = false; } else if (rhs.HasValue()) { - std::destroy_at(std::addressof(unex_)); + std::destroy_at(std::addressof(Unex())); has_val_ = true; } else { - unex_ = std::move(rhs.Error()); + Unex() = std::move(rhs.Error()); } return *this; } @@ -1078,11 +1095,11 @@ class Expected { std::is_assignable_v constexpr Expected& operator=(const Unexpected& e) { if (HasValue()) { - std::construct_at(std::addressof(unex_), + std::construct_at(std::addressof(Unex()), std::forward(e.Error())); has_val_ = false; } else { - unex_ = std::forward(e.Error()); + Unex() = std::forward(e.Error()); } return *this; } @@ -1091,17 +1108,17 @@ class Expected { requires std::is_constructible_v && std::is_assignable_v constexpr Expected& operator=(Unexpected&& e) { if (HasValue()) { - std::construct_at(std::addressof(unex_), std::forward(e.Error())); + std::construct_at(std::addressof(Unex()), std::forward(e.Error())); has_val_ = false; } else { - unex_ = std::forward(e.Error()); + Unex() = std::forward(e.Error()); } return *this; } constexpr void Emplace() noexcept { if (!HasValue()) { - std::destroy_at(std::addressof(unex_)); + std::destroy_at(std::addressof(Unex())); has_val_ = true; } } @@ -1113,15 +1130,15 @@ class Expected { { if (HasValue() && rhs.HasValue()) { } else if (HasValue()) { - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - std::destroy_at(std::addressof(rhs.unex_)); + std::construct_at(std::addressof(Unex()), std::move(rhs.Unex())); + std::destroy_at(std::addressof(rhs.Unex())); has_val_ = false; rhs.has_val_ = true; } else if (rhs.HasValue()) { rhs.swap(*this); } else { using std::swap; - swap(unex_, rhs.unex_); + swap(Unex(), rhs.Unex()); } } @@ -1136,28 +1153,26 @@ class Expected { constexpr bool HasValue() const noexcept { return has_val_; } - constexpr void operator*() const noexcept { - TERMITE_ASSERT(has_val_); - } + constexpr void operator*() const noexcept { TERMITE_ASSERT(has_val_); } constexpr const E& Error() const& noexcept { TERMITE_ASSERT(!has_val_); - return unex_; + return Unex(); } constexpr E& Error() & noexcept { TERMITE_ASSERT(!has_val_); - return unex_; + return Unex(); } constexpr E&& Error() && noexcept { TERMITE_ASSERT(!has_val_); - return std::move(unex_); + return std::move(Unex()); } constexpr const E&& Error() const&& noexcept { TERMITE_ASSERT(!has_val_); - return std::move(unex_); + return std::move(Unex()); } template @@ -1426,8 +1441,20 @@ class Expected { } private: + constexpr E& Unex() & noexcept { return *reinterpret_cast(unex_raw_); } + + constexpr const E& Unex() const& noexcept { + return *reinterpret_cast(unex_raw_); + } + + constexpr E&& Unex() && noexcept { return *reinterpret_cast(unex_raw_); } + + constexpr const E&& Unex() const&& noexcept { + return *reinterpret_cast(unex_raw_); + } + union { - E unex_; + alignas(alignof(E)) std::byte unex_raw_[sizeof(E)]; }; bool has_val_; }; diff --git a/test/expected_test.cpp b/test/expected_test.cpp index 196598c..6117d9f 100644 --- a/test/expected_test.cpp +++ b/test/expected_test.cpp @@ -131,4 +131,9 @@ TEST_CASE("Expected test suites", "[expected]") { REQUIRE(!e); REQUIRE(e.Error() == 12); } + { + Expected e = Unexpected(42); + REQUIRE(!e); + REQUIRE(e.Error() == 42); + } }