Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
66ce386
start work on Result class
SizzinSeal Mar 20, 2025
5878600
remove const qualifier from Result member variables
SizzinSeal Mar 28, 2025
e0c4824
move ResultError type member to the end of the struct
SizzinSeal Mar 28, 2025
69bb041
add const qualifier to parse member function of ResultError formatter
SizzinSeal Mar 28, 2025
bdecefa
make << operator use std::format for ResultError
SizzinSeal Mar 28, 2025
641d235
add some "flair" to result stacktrace formatting
SizzinSeal Mar 28, 2025
326b332
more consistent template parameter typing in result.hpp
SizzinSeal Mar 28, 2025
2fba9c7
remove Result move constructor, as it's equivalent to the implicit mo…
SizzinSeal Mar 28, 2025
76b7c67
add option for forcing explicit unwrapping of Result
SizzinSeal Mar 29, 2025
f8cdc43
fix meson language server ramblings
SizzinSeal Mar 29, 2025
ac4609e
use error types in Result class
SizzinSeal Apr 27, 2025
ac561fe
mark Result constructor as explicit
SizzinSeal Apr 28, 2025
1f2ac91
improve sentinel trait naming
SizzinSeal Apr 28, 2025
3d2fdd2
clean up Result constraints
SizzinSeal Apr 29, 2025
edf7646
improve get member function of Result
SizzinSeal Apr 29, 2025
1033540
specialize Result with void type
SizzinSeal Apr 29, 2025
d83b85f
require at least one error type in the Result class
SizzinSeal Apr 29, 2025
15eb38d
fix Result constructor warnings
SizzinSeal Apr 29, 2025
67f6c8a
add comparison operator overload to Result
SizzinSeal Apr 29, 2025
83328b2
Merge branch 'main' into feat/better-errors
SizzinSeal Apr 29, 2025
84f3e7e
Merge branch 'feat/better-errors' of https://github.com/ZestCommunity…
SizzinSeal Apr 29, 2025
99398cf
improve Result comparison operator
SizzinSeal Apr 29, 2025
dc99edf
fix SentinelValue specialization for integral types
SizzinSeal Apr 29, 2025
41c3d29
fix Result ctor order warnings
SizzinSeal Apr 29, 2025
7d4eea4
add time point to ResultError
SizzinSeal Apr 29, 2025
3628c58
improve Result error type constraints
SizzinSeal Apr 30, 2025
d5a4999
use infinity as a sentinel value if possible
SizzinSeal Apr 30, 2025
479f1c4
fix bad variant access in Result::get
SizzinSeal Apr 30, 2025
953874f
fix SentinelValue floating-point values
SizzinSeal May 1, 2025
c0408dc
use doxygen documentation in result.hpp
SizzinSeal May 1, 2025
f397080
use std::remove_cvref instead of std::remove_cvref_t in result.hpp
SizzinSeal May 1, 2025
7d1792e
fix ambiguous Result comparison operator
SizzinSeal May 2, 2025
a930af2
add comparison operator tests for Result
SizzinSeal May 2, 2025
7a86847
add Result void specialization test
SizzinSeal May 2, 2025
ce756f7
remove `constexpr` qualifiers for certain `Result` member functions
SizzinSeal May 2, 2025
11d3b0d
support compile-time ResultError
SizzinSeal May 2, 2025
5f1ff0b
use proper move semantics for Result conversion operators
SizzinSeal May 2, 2025
d41d766
remove Result conversion operators returning r-value references
SizzinSeal May 2, 2025
255afd4
fix Result conversion operator qualifiers again
SizzinSeal May 2, 2025
b329187
add const-rvalue reference overload for Result::get
SizzinSeal May 2, 2025
29c8fa6
rename Result.cpp to result.cpp
SizzinSeal May 2, 2025
36f16db
fix Result::get return types
SizzinSeal May 3, 2025
44b326e
fix meson language server tweaking
SizzinSeal May 3, 2025
3a07636
fix Result::get return type
SizzinSeal May 3, 2025
b6a854a
add more Result compile-time tests
SizzinSeal May 3, 2025
92bdad1
add second Result test function
SizzinSeal May 3, 2025
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
146 changes: 146 additions & 0 deletions include/common/result.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#pragma once

#include <concepts>
#include <limits>
#include <optional>
#include <stacktrace>
#include <type_traits>
#include <variant>

namespace zest {

// custom error types inherit from the ResultError class, which enforces that custom error types
// have some shared functionality.
class ResultError {
public:
// since this constructor has no arguments, it'll be called implicitly by the constructor of any
// child class.
ResultError()
: stacktrace(std::stacktrace::current()) {}

std::stacktrace stacktrace;
};

// concept used to enforce that error types inherit from the ResultError class
template<typename T>
concept CustomError = std::derived_from<T, ResultError>;

// Some primitive types such as float, double, etc may have a standardized "sentinel" value.
// For example, a function that returns a float may return INFINITY if an error occurs.
// Usually the maximum value of the type is returned if there is an error.
// This trait helps simplify DX.
// This trait can also be specialized for custom types (e.g unitized types)
template<typename T>
class HasSentinel;

// concept which can be used to determine if a type has a sentinel value
template<typename T>
concept Sentinel = requires(const T& val) { HasSentinel<T>::value; };

// templated variable which can be used to simplify usage of HasSentinel
template<Sentinel T>
constexpr T sentinel_v = HasSentinel<T>::value;

// partial specialization for HasSentinel.
// any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value
template<std::integral T>
class HasSentinel<T> {
static constexpr T value = std::numeric_limits<T>::max();
};

// Result class.
// An alternative to std::expected, where an expected value can be contained alongside an unexpected
// error value.
// This means that Result will always contain an expected value.
// Therefore, if the user does not check if a function returned an error, an exception will NOT be
// thrown, and therefore the thread won't crash.
template<typename T, CustomError... Errs>
class Result {
public:
// Construct a Result with a value and no error value.
// Constraint: the value member variable must be able to be constructed with the value
// parameter.
template<typename U>
requires std::constructible_from<T, U>
constexpr Result(U&& value)
: value(std::forward<U>(value)) {}

// Construct a Result with a value and an error value.
// Constraint: the value member variable must be able to be constructed with the value
// parameter.
// Constraint: the error parameter must be of a type that may be contained in the error member
// variable.
template<typename U, CustomError E>
requires std::constructible_from<T, U>
&& (std::is_same_v<Errs, std::remove_cvref<E>> || ...)
constexpr Result(U&& value, E&& error)
: value(std::forward<U>(value)),
error(std::forward<E>(error)) {}

// Construct a Result with an error, initializing the normal value to its sentinel value.
// Constraint: the type of the normal value must have a specialized sentinel value.
// Constraint: the error parameter must be of a type that may be contained in the error member
// variable.
template<CustomError U>
requires Sentinel<T> && (std::is_same_v<Errs, std::remove_cvref<U>> || ...)
constexpr Result(U&& error)
: value(sentinel_v<T>),
error(std::forward<U>(error)) {}

// Check whether the result contains an error of the given type
// Constraint: the custom error type to check for must be able to be contained in the error
// member variable.
template<CustomError U>
requires(std::is_same_v<Errs, std::remove_cvref<U>> || ...)
constexpr U has() const& {
if (!error.has_value()) {
return false;
} else {
return std::holds_alternative<U>(error);
}
};

// return an optional wrapping the given error type.
// Constraint: the custom error type to get must be able to be contained in the error
// member variable.
template<CustomError U>
requires(std::is_same_v<Errs, std::remove_cvref<U>> || ...)
constexpr std::optional<U> get() const& {
if (this->has<U>()) {
return std::get<U>(error.value());
} else {
return std::nullopt;
}
}

// return an optional wrapping the given error type.
// Constraint: the custom error type to get must be able to be contained in the error
// member variable.
template<CustomError U>
requires(std::is_same_v<Errs, std::remove_cvref<U>> || ...)
constexpr std::optional<U> get() && {
if (this->has<U>()) {
return std::get<U>(std::move(error.value()));
} else {
return std::nullopt;
}
}

// implicit conversion operator, on an l-value
constexpr operator T() const& {
return value;
};

// implicit conversion operator, on an r-value
constexpr operator T() && {
return std::move(value);
}

// the optional error value.
// a variant that could contain any of the specified error types.
std::optional<std::variant<Errs...>> error;
// the normal value
T value;
};

} // namespace zest
14 changes: 10 additions & 4 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ linker_flags = [
# system libraries we depend on
system_deps = [
'-nostartfiles', # we still need to implement some newlib stubs
'-lstdc++exp',
]
add_global_link_arguments( system_deps, language: 'c')
add_global_link_arguments(system_deps, language: 'cpp')
Expand All @@ -73,11 +74,16 @@ stdlib_conf = [
'-D_POSIX_MONOTONIC_CLOCK', # enable the POSIX monotonic clock
]

# warning flags
warning_flags = [
'-Wno-psabi' # all libraries (except libv5) are compiled from source, making this warning useless
]

# apply all these flags and configs
add_global_arguments(optimization_flags, formatting_flags, stdlib_conf, language: 'c')
add_global_arguments(optimization_flags, formatting_flags, stdlib_conf, language: 'cpp')
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, system_deps, language: 'c')
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, system_deps, language: 'cpp')
add_global_arguments(optimization_flags, formatting_flags, warning_flags, stdlib_conf, language: 'c')
add_global_arguments(optimization_flags, formatting_flags, warning_flags, stdlib_conf, language: 'cpp')
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, warning_flags, system_deps, language: 'c')
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, warning_flags, system_deps, language: 'cpp')

# include directories.
# we only specify the top level in order to enforce paths in include directives.
Expand Down
Loading