Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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()
Expand Down
39 changes: 8 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <termite/expected.h>;
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 <termite/expected.h>; 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.
99 changes: 63 additions & 36 deletions include/termite/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>(cond)) { \
TERMITE_UNREACHABLE; \
TERMITE_UNREACHABLE; \
} \
} while (false)
#endif
Expand Down Expand Up @@ -947,7 +947,7 @@ class Expected<T, E> {
(!std::is_trivially_copy_constructible_v<E>)
: has_val_(rhs.HasValue()) {
if (!rhs.HasValue()) {
std::construct_at(std::addressof(unex_), rhs.Error());
std::construct_at(std::addressof(Unex()), rhs.Error());
}
}

Expand All @@ -962,7 +962,7 @@ class Expected<T, E> {
(!std::is_trivially_move_constructible_v<E>)
: 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()));
}
}

Expand All @@ -979,7 +979,7 @@ class Expected<T, E> {
Expected(const Expected<U, G>& rhs)
: has_val_(rhs.HasValue()) {
if (!rhs.HasValue()) {
std::construct_at(std::addressof(unex_),
std::construct_at(std::addressof(Unex()),
std::forward<const G&>(rhs.Error()));
}
}
Expand All @@ -991,37 +991,54 @@ class Expected<T, E> {
Expected(Expected<U, G>&& rhs)
: has_val_(rhs.HasValue()) {
if (!rhs.HasValue()) {
std::construct_at(std::addressof(unex_), std::forward<G>(rhs.Error()));
std::construct_at(std::addressof(Unex()), std::forward<G>(rhs.Error()));
}
}

template <typename G>
requires std::is_constructible_v<E, const G&>
constexpr explicit(!std::is_convertible_v<const G&, E>)
Expected(const Unexpected<G>& e)
: unex_(std::forward<const G&>(e.Error())), has_val_(false) {}
: has_val_(false) {
if (!HasValue()) {
std::construct_at(std::addressof(Unex()),
std::forward<const G&>(e.Error()));
}
}

template <typename G>
requires std::is_constructible_v<E, G>
constexpr explicit(!std::is_convertible_v<G, E>) Expected(Unexpected<G>&& e)
: unex_(std::forward<G>(e.Error())), has_val_(false) {}
: has_val_(false) {
if (!HasValue()) {
std::construct_at(std::addressof(Unex()), std::forward<G>(e.Error()));
}
}

constexpr explicit Expected(InPlaceTag) noexcept : has_val_(true) {}

template <typename... Args>
requires std::is_constructible_v<E, Args...>
constexpr explicit Expected(UnexpectTag, Args&&... args)
: unex_(std::forward<Args>(args)...), has_val_(false) {}
constexpr explicit Expected(UnexpectTag, Args&&... args) : has_val_(false) {
if (!HasValue()) {
std::construct_at(std::addressof(Unex()), std::forward<Args>(args)...);
}
}

template <typename U, typename... Args>
requires std::is_constructible_v<E, std::initializer_list<U>&, Args...>
constexpr explicit Expected(UnexpectTag, std::initializer_list<U> il,
Args&&... args)
: unex_(il, std::forward<Args>(args)...), has_val_(false) {}
: has_val_(false) {
if (!HasValue()) {
std::construct_at(std::addressof(Unex()), il,
std::forward<Args>(args)...);
}
}

constexpr ~Expected() {
if (!HasValue()) {
std::destroy_at(std::addressof(unex_));
std::destroy_at(std::addressof(Unex()));
}
}

Expand All @@ -1040,13 +1057,13 @@ class Expected<T, E> {

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;
}
Expand All @@ -1062,13 +1079,13 @@ class Expected<T, E> {

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;
}
Expand All @@ -1078,11 +1095,11 @@ class Expected<T, E> {
std::is_assignable_v<E&, const G&>
constexpr Expected& operator=(const Unexpected<G>& e) {
if (HasValue()) {
std::construct_at(std::addressof(unex_),
std::construct_at(std::addressof(Unex()),
std::forward<const G&>(e.Error()));
has_val_ = false;
} else {
unex_ = std::forward<const G&>(e.Error());
Unex() = std::forward<const G&>(e.Error());
}
return *this;
}
Expand All @@ -1091,17 +1108,17 @@ class Expected<T, E> {
requires std::is_constructible_v<E, G> && std::is_assignable_v<E&, G>
constexpr Expected& operator=(Unexpected<G>&& e) {
if (HasValue()) {
std::construct_at(std::addressof(unex_), std::forward<G>(e.Error()));
std::construct_at(std::addressof(Unex()), std::forward<G>(e.Error()));
has_val_ = false;
} else {
unex_ = std::forward<G>(e.Error());
Unex() = std::forward<G>(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;
}
}
Expand All @@ -1113,15 +1130,15 @@ class Expected<T, E> {
{
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());
}
}

Expand All @@ -1136,28 +1153,26 @@ class Expected<T, E> {

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 <typename G = E>
Expand Down Expand Up @@ -1426,8 +1441,20 @@ class Expected<T, E> {
}

private:
constexpr E& Unex() & noexcept { return *reinterpret_cast<E*>(unex_raw_); }

constexpr const E& Unex() const& noexcept {
return *reinterpret_cast<const E*>(unex_raw_);
}

constexpr E&& Unex() && noexcept { return *reinterpret_cast<E*>(unex_raw_); }

constexpr const E&& Unex() const&& noexcept {
return *reinterpret_cast<const E*>(unex_raw_);
}

union {
E unex_;
alignas(alignof(E)) std::byte unex_raw_[sizeof(E)];
};
bool has_val_;
};
Expand Down
5 changes: 5 additions & 0 deletions test/expected_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,9 @@ TEST_CASE("Expected test suites", "[expected]") {
REQUIRE(!e);
REQUIRE(e.Error() == 12);
}
{
Expected<void, int> e = Unexpected(42);
REQUIRE(!e);
REQUIRE(e.Error() == 42);
}
}