From 66ce386c966d74eeb7d1647cd037602d85a065bd Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Thu, 20 Mar 2025 13:20:47 -0700 Subject: [PATCH 01/44] start work on Result class --- include/common/result.hpp | 114 ++++++++++++++++++++++++++++++++++++++ meson.build | 16 ++++-- 2 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 include/common/result.hpp diff --git a/include/common/result.hpp b/include/common/result.hpp new file mode 100644 index 00000000..68f40975 --- /dev/null +++ b/include/common/result.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace zest { + +template + requires std::is_scoped_enum_v +struct ResultError { + E type; + std::string message; + std::stacktrace stacktrace; +}; + +/** + * @brief Result class, used for error handling. + * + * The Result class can contain a value and optionally an error. Unlike std::expected, the value is + * ALWAYS valid. This means that a failure to handle an error by the user will not crash the thread. + * + * Instances of this class can be implicitly converted to the "normal" type, so UX is not + * complicated at all. + * + * @tparam T the "normal" value. + * @tparam E the error type, has to be an enum class. + * + * @b Example + * @code + * enum class ExampleError { + * ExampleA, + * ExampleB, + * }; + * + * zest::Result get_double() { + * // for the purposes of this example, this function will always return an error + * return {INFINITY, ExampleError::ExampleA, "I can be formatted{}", "!!!"}; + * } + * + * void initialize() { + * } + * + * @endcode + */ +template + requires std::is_scoped_enum_v +class Result { + public: + template + requires std::constructible_from + Result(U&& val) + : val(std::forward(val)) {} + + Result(Result&& other) + : val(std::move(other.val)), + error(std::move(other.error)) {} + + template + requires std::constructible_from && std::constructible_from, F> + Result(U&& val, F&& error) + : val(std::forward(val)), + error(std::forward(error)) {} + + template + requires std::constructible_from + Result(U&& val, E type, std::format_string fmt, Args&&... args) + : val(std::forward(val)), + error({type, std::format(fmt, std::forward(args)...), std::stacktrace::current()}) { + } + + operator T() const& { + return val; + } + + operator T() && { + return std::move(val); + } + + const T val; + const std::optional> error; +}; + +} // namespace zest + +template +struct std::formatter> { + constexpr auto parse(std::format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(const zest::ResultError& error, std::format_context& ctx) const { + return std::format_to( + ctx.out(), + "{}\n" + "begin stacktrace\n" + "{}\n" + "end stacktrace", + error.message, + error.stacktrace + ); + } +}; + +template +std::ostream& operator<<(std::ostream& os, const zest::ResultError& error) { + os << error.message << std::endl + << "begin stacktrace" << std::endl + << error.stacktrace << std::endl + << "end stacktrace"; + return os; +} \ No newline at end of file diff --git a/meson.build b/meson.build index af4d8e78..c666f6a5 100644 --- a/meson.build +++ b/meson.build @@ -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') @@ -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. @@ -109,4 +115,4 @@ custom_target( input: elf, build_by_default: true, # otherwise it won't be built command: [objcopy, ['-O', 'binary', '-S', '@INPUT@', '@OUTPUT@']], -) \ No newline at end of file +) From 5878600820cbbc501f7b99c5a17ee4472e88a92f Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 16:27:52 -0700 Subject: [PATCH 02/44] remove const qualifier from Result member variables --- include/common/result.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 68f40975..a1014ab5 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -79,8 +79,8 @@ class Result { return std::move(val); } - const T val; - const std::optional> error; + T val; + std::optional> error; }; } // namespace zest From e0c482446e2b181ab8b29757347e2fb395195e00 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 16:29:32 -0700 Subject: [PATCH 03/44] move ResultError type member to the end of the struct --- include/common/result.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index a1014ab5..322b75a9 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -11,9 +11,9 @@ namespace zest { template requires std::is_scoped_enum_v struct ResultError { - E type; std::string message; std::stacktrace stacktrace; + E type; }; /** From 69bb04182a58d4ec7e0c6e51a8192d818bb8d5de Mon Sep 17 00:00:00 2001 From: Liam Teale <111480281+SizzinSeal@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:30:49 -0700 Subject: [PATCH 04/44] add const qualifier to parse member function of ResultError formatter Co-authored-by: ion098 <146852218+ion098@users.noreply.github.com> --- include/common/result.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 322b75a9..69d5079f 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -87,7 +87,7 @@ class Result { template struct std::formatter> { - constexpr auto parse(std::format_parse_context& ctx) { + constexpr auto parse(std::format_parse_context& ctx) const { return ctx.begin(); } From bdecefa96cfe22140942585de296dcc0b4ba605d Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 16:35:54 -0700 Subject: [PATCH 05/44] make << operator use std::format for ResultError --- include/common/result.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 69d5079f..aa3054b1 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -106,9 +106,6 @@ struct std::formatter> { template std::ostream& operator<<(std::ostream& os, const zest::ResultError& error) { - os << error.message << std::endl - << "begin stacktrace" << std::endl - << error.stacktrace << std::endl - << "end stacktrace"; + os << std::format("{}", error); return os; } \ No newline at end of file From 641d2353472f715755886693a3bf5d09d362da2a Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 16:37:08 -0700 Subject: [PATCH 06/44] add some "flair" to result stacktrace formatting --- include/common/result.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index aa3054b1..3c47f9d9 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -95,9 +95,9 @@ struct std::formatter> { return std::format_to( ctx.out(), "{}\n" - "begin stacktrace\n" + "<== BEGIN STACKTRACE ==>\n" "{}\n" - "end stacktrace", + "<== END STACKTRACE ==>", error.message, error.stacktrace ); From 326b3324f255dd000306744bc29b6789478dbcd2 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 16:39:08 -0700 Subject: [PATCH 07/44] more consistent template parameter typing in result.hpp --- include/common/result.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 3c47f9d9..1ccc5e3c 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -8,12 +8,12 @@ namespace zest { -template - requires std::is_scoped_enum_v +template + requires std::is_scoped_enum_v struct ResultError { std::string message; std::stacktrace stacktrace; - E type; + T type; }; /** @@ -85,13 +85,13 @@ class Result { } // namespace zest -template -struct std::formatter> { +template +struct std::formatter> { constexpr auto parse(std::format_parse_context& ctx) const { return ctx.begin(); } - auto format(const zest::ResultError& error, std::format_context& ctx) const { + auto format(const zest::ResultError& error, std::format_context& ctx) const { return std::format_to( ctx.out(), "{}\n" @@ -104,8 +104,8 @@ struct std::formatter> { } }; -template -std::ostream& operator<<(std::ostream& os, const zest::ResultError& error) { +template +std::ostream& operator<<(std::ostream& os, const zest::ResultError& error) { os << std::format("{}", error); return os; } \ No newline at end of file From 2fba9c7b5e06e0560584f5264fb6177515694404 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 16:56:45 -0700 Subject: [PATCH 08/44] remove Result move constructor, as it's equivalent to the implicit move constructor --- include/common/result.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 1ccc5e3c..179c765e 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -54,10 +54,6 @@ class Result { Result(U&& val) : val(std::forward(val)) {} - Result(Result&& other) - : val(std::move(other.val)), - error(std::move(other.error)) {} - template requires std::constructible_from && std::constructible_from, F> Result(U&& val, F&& error) From 76b7c676aea014e5ccd034d1aafac07fa82a4deb Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 28 Mar 2025 18:51:49 -0700 Subject: [PATCH 09/44] add option for forcing explicit unwrapping of Result --- include/common/result.hpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 179c765e..437db4bb 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -6,6 +6,17 @@ #include #include +// library developers and advanced users may appreciate compiler checks to ensure they are actually +// handling all errors. +// By defining EXPLICIT_RESULTS, the Result conversion operators will be marked as explicit. +// This does not break ABI, so libraries/projects dependencies can define EXPLICIT_RESULTS or not +// and still be compatible. +#ifndef EXPLICIT_RESULTS + #define RESULT_EXPLICIT_QUALIFIER +#else + #define RESULT_EXPLICIT_QUALIFIER explicit +#endif + namespace zest { template @@ -67,11 +78,11 @@ class Result { error({type, std::format(fmt, std::forward(args)...), std::stacktrace::current()}) { } - operator T() const& { + RESULT_EXPLICIT_QUALIFIER operator T() const& { return val; } - operator T() && { + RESULT_EXPLICIT_QUALIFIER operator T() && { return std::move(val); } From f8cdc434d542d576080db3b1d3f83cb9f2a8e2ab Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Sat, 29 Mar 2025 00:28:43 -0700 Subject: [PATCH 10/44] fix meson language server ramblings --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index c666f6a5..824a91c1 100644 --- a/meson.build +++ b/meson.build @@ -115,4 +115,4 @@ custom_target( input: elf, build_by_default: true, # otherwise it won't be built command: [objcopy, ['-O', 'binary', '-S', '@INPUT@', '@OUTPUT@']], -) +) \ No newline at end of file From ac4609ef9e1ed46232b4eb1f5b68644a50b318ed Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Sun, 27 Apr 2025 14:13:04 -0700 Subject: [PATCH 11/44] use error types in Result class --- include/common/result.hpp | 210 +++++++++++++++++++++----------------- 1 file changed, 119 insertions(+), 91 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 437db4bb..b4f39a65 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -1,118 +1,146 @@ #pragma once -#include -#include +#include +#include #include #include #include - -// library developers and advanced users may appreciate compiler checks to ensure they are actually -// handling all errors. -// By defining EXPLICIT_RESULTS, the Result conversion operators will be marked as explicit. -// This does not break ABI, so libraries/projects dependencies can define EXPLICIT_RESULTS or not -// and still be compatible. -#ifndef EXPLICIT_RESULTS - #define RESULT_EXPLICIT_QUALIFIER -#else - #define RESULT_EXPLICIT_QUALIFIER explicit -#endif +#include namespace zest { -template - requires std::is_scoped_enum_v -struct ResultError { - std::string message; +// 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; - T type; }; -/** - * @brief Result class, used for error handling. - * - * The Result class can contain a value and optionally an error. Unlike std::expected, the value is - * ALWAYS valid. This means that a failure to handle an error by the user will not crash the thread. - * - * Instances of this class can be implicitly converted to the "normal" type, so UX is not - * complicated at all. - * - * @tparam T the "normal" value. - * @tparam E the error type, has to be an enum class. - * - * @b Example - * @code - * enum class ExampleError { - * ExampleA, - * ExampleB, - * }; - * - * zest::Result get_double() { - * // for the purposes of this example, this function will always return an error - * return {INFINITY, ExampleError::ExampleA, "I can be formatted{}", "!!!"}; - * } - * - * void initialize() { - * } - * - * @endcode - */ -template - requires std::is_scoped_enum_v +// concept used to enforce that error types inherit from the ResultError class +template +concept CustomError = std::derived_from; + +// 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 +class HasSentinel; + +// concept which can be used to determine if a type has a sentinel value +template +concept Sentinel = requires(const T& val) { HasSentinel::value; }; + +// templated variable which can be used to simplify usage of HasSentinel +template +constexpr T sentinel_v = HasSentinel::value; + +// partial specialization for HasSentinel. +// any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value +template +class HasSentinel { + static constexpr T value = std::numeric_limits::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 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 requires std::constructible_from - Result(U&& val) - : val(std::forward(val)) {} - - template - requires std::constructible_from && std::constructible_from, F> - Result(U&& val, F&& error) - : val(std::forward(val)), - error(std::forward(error)) {} + constexpr Result(U&& value) + : value(std::forward(value)) {} - template + // 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 requires std::constructible_from - Result(U&& val, E type, std::format_string fmt, Args&&... args) - : val(std::forward(val)), - error({type, std::format(fmt, std::forward(args)...), std::stacktrace::current()}) { - } + && (std::is_same_v> || ...) + constexpr Result(U&& value, E&& error) + : value(std::forward(value)), + error(std::forward(error)) {} - RESULT_EXPLICIT_QUALIFIER operator T() const& { - return val; - } + // 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 + requires Sentinel && (std::is_same_v> || ...) + constexpr Result(U&& error) + : value(sentinel_v), + error(std::forward(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 + requires(std::is_same_v> || ...) + constexpr U has() const& { + if (!error.has_value()) { + return false; + } else { + return std::holds_alternative(error); + } + }; - RESULT_EXPLICIT_QUALIFIER operator T() && { - return std::move(val); + // 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 + requires(std::is_same_v> || ...) + constexpr std::optional get() const& { + if (this->has()) { + return std::get(error.value()); + } else { + return std::nullopt; + } } - T val; - std::optional> 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 + requires(std::is_same_v> || ...) + constexpr std::optional get() && { + if (this->has()) { + return std::get(std::move(error.value())); + } else { + return std::nullopt; + } + } -} // namespace zest + // implicit conversion operator, on an l-value + constexpr operator T() const& { + return value; + }; -template -struct std::formatter> { - constexpr auto parse(std::format_parse_context& ctx) const { - return ctx.begin(); + // implicit conversion operator, on an r-value + constexpr operator T() && { + return std::move(value); } - auto format(const zest::ResultError& error, std::format_context& ctx) const { - return std::format_to( - ctx.out(), - "{}\n" - "<== BEGIN STACKTRACE ==>\n" - "{}\n" - "<== END STACKTRACE ==>", - error.message, - error.stacktrace - ); - } + // the optional error value. + // a variant that could contain any of the specified error types. + std::optional> error; + // the normal value + T value; }; -template -std::ostream& operator<<(std::ostream& os, const zest::ResultError& error) { - os << std::format("{}", error); - return os; -} \ No newline at end of file +} // namespace zest \ No newline at end of file From ac561fe909e23099d686d567f7cc0179fdc59e1f Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 14:37:43 -0700 Subject: [PATCH 12/44] mark Result constructor as explicit --- include/common/result.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index b4f39a65..5ffd1c02 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -83,7 +83,7 @@ class Result { // variable. template requires Sentinel && (std::is_same_v> || ...) - constexpr Result(U&& error) + explicit constexpr Result(U&& error) : value(sentinel_v), error(std::forward(error)) {} From 1f2ac9123adc5903a94a5efedf60cb70fcadd925 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 14:49:32 -0700 Subject: [PATCH 13/44] improve sentinel trait naming --- include/common/result.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 5ffd1c02..1909b505 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -31,20 +31,20 @@ concept CustomError = std::derived_from; // This trait helps simplify DX. // This trait can also be specialized for custom types (e.g unitized types) template -class HasSentinel; +class SentinelValue; // concept which can be used to determine if a type has a sentinel value template -concept Sentinel = requires(const T& val) { HasSentinel::value; }; +concept Sentinel = requires(const T& val) { SentinelValue::value; }; // templated variable which can be used to simplify usage of HasSentinel template -constexpr T sentinel_v = HasSentinel::value; +constexpr T sentinel_v = SentinelValue::value; // partial specialization for HasSentinel. // any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value template -class HasSentinel { +class SentinelValue { static constexpr T value = std::numeric_limits::max(); }; @@ -57,6 +57,8 @@ class HasSentinel { template class Result { public: + // concept + // 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. From 3d2fdd20ac4452577b01b9a328a3ed65f5e4e6b6 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 17:06:53 -0700 Subject: [PATCH 14/44] clean up Result constraints --- include/common/result.hpp | 90 ++++++++++++++------------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 1909b505..03592174 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include namespace zest { @@ -21,10 +20,6 @@ class ResultError { std::stacktrace stacktrace; }; -// concept used to enforce that error types inherit from the ResultError class -template -concept CustomError = std::derived_from; - // 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. @@ -54,79 +49,57 @@ class SentinelValue { // 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 +template + requires(std::derived_from && ...) class Result { public: - // concept - // 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. + // Constraint: type T can be constructed from type U template requires std::constructible_from constexpr Result(U&& value) : value(std::forward(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 - requires std::constructible_from - && (std::is_same_v> || ...) + // Constraint: type T can be constructed from type U + // Constraint: some type in Errs can be constructed from type E + template + requires std::constructible_from && (std::constructible_from || ...) constexpr Result(U&& value, E&& error) : value(std::forward(value)), error(std::forward(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 - requires Sentinel && (std::is_same_v> || ...) - explicit constexpr Result(U&& error) + // Constraint: type T has a sentinel value + // Constraint: some type in Errs can be constructed from type U + // Constraint: type U can't be implicitly converted to type T + template + requires Sentinel && (std::constructible_from || ...) + && (!std::convertible_to) + constexpr Result(U&& error) : value(sentinel_v), error(std::forward(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 - requires(std::is_same_v> || ...) - constexpr U has() const& { - if (!error.has_value()) { - return false; - } else { - return std::holds_alternative(error); - } - }; + // Construct a Result with an error, initializing the normal value to its sentinel value. + // Constraint: type T has a sentinel value + // Constraint: some type in Errs can be constructed from type U + template + requires Sentinel && (std::constructible_from || ...) + explicit constexpr Result(U&& error) + : value(sentinel_v), + error(std::forward(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 - requires(std::is_same_v> || ...) + // Check whether the result contains an error of the given type. + // Constraint: some type Errs can be constructed from type U + template + requires(std::constructible_from || ...) constexpr std::optional get() const& { - if (this->has()) { - return std::get(error.value()); - } else { + if (std::holds_alternative(error)) { 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 - requires(std::is_same_v> || ...) - constexpr std::optional get() && { - if (this->has()) { - return std::get(std::move(error.value())); } else { - return std::nullopt; + return std::get(error); } - } + }; // implicit conversion operator, on an l-value constexpr operator T() const& { @@ -138,9 +111,8 @@ class Result { return std::move(value); } - // the optional error value. - // a variant that could contain any of the specified error types. - std::optional> error; + // a variant that could contain any of the specified error types + std::variant error; // the normal value T value; }; From edf764610ff7219b81c85cdb9141c0153325f57c Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 17:29:48 -0700 Subject: [PATCH 15/44] improve get member function of Result --- include/common/result.hpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 03592174..419e6c88 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -89,10 +89,10 @@ class Result { : value(sentinel_v), error(std::forward(error)) {} - // Check whether the result contains an error of the given type. + // Get the given error type, if it exists // Constraint: some type Errs can be constructed from type U template - requires(std::constructible_from || ...) + requires(std::same_as || ...) constexpr std::optional get() const& { if (std::holds_alternative(error)) { return std::nullopt; @@ -101,12 +101,38 @@ class Result { } }; - // implicit conversion operator, on an l-value + // Get the given error type, if it exists + // Constraint: some type Errs can be constructed from type U + template + requires(std::same_as || ...) + constexpr std::optional get() && { + if (std::holds_alternative(error)) { + return std::nullopt; + } else { + return std::move(std::get(error)); + } + }; + + // get the normal value + template + requires std::same_as + constexpr T get() const& { + return value; + } + + // get the normal value + template + requires std::same_as + constexpr T get() && { + return std::move(value); + } + + // implicit conversion operator constexpr operator T() const& { return value; }; - // implicit conversion operator, on an r-value + // implicit conversion operator constexpr operator T() && { return std::move(value); } From 10335405c46a9e81d8524d573cb1d8f3ce49c499 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 17:54:48 -0700 Subject: [PATCH 16/44] specialize Result with void type --- include/common/result.hpp | 43 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 419e6c88..2a1c266e 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -58,7 +58,8 @@ class Result { template requires std::constructible_from constexpr Result(U&& value) - : value(std::forward(value)) {} + : value(std::forward(value)), + error(std::monostate()) {} // Construct a Result with a value and an error value. // Constraint: type T can be constructed from type U @@ -143,4 +144,44 @@ class Result { T value; }; +template + requires(std::derived_from && ...) +class Result { + public: + // construct with an error value + template + requires(std::constructible_from || ...) + constexpr Result(U&& error) + : error(std::forward(error)) {} + + // construct with no error value + constexpr Result() + : error(std::monostate()) {} + + // Get the given error type, if it exists + // Constraint: some type Errs can be constructed from type U + template + requires(std::same_as || ...) + constexpr std::optional get() const& { + if (std::holds_alternative(error)) { + return std::nullopt; + } else { + return std::get(error); + } + }; + + // Get the given error type, if it exists + // Constraint: some type Errs can be constructed from type U + template + requires(std::same_as || ...) + constexpr std::optional get() && { + if (std::holds_alternative(error)) { + return std::nullopt; + } else { + return std::move(std::get(error)); + } + }; + + std::variant error; +}; } // namespace zest \ No newline at end of file From d83b85f30bec860107ee5894cda453b36d7edb2a Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 18:13:46 -0700 Subject: [PATCH 17/44] require at least one error type in the Result class --- include/common/result.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 2a1c266e..ccc70c9a 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -50,7 +50,7 @@ class SentinelValue { // 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 - requires(std::derived_from && ...) + requires(sizeof...(Errs) > 0) && (std::derived_from && ...) class Result { public: // Construct a Result with a value and no error value. @@ -145,7 +145,7 @@ class Result { }; template - requires(std::derived_from && ...) + requires(sizeof...(Errs) > 0) && (std::derived_from && ...) class Result { public: // construct with an error value From 15eb38d7551231a768c131ec121dbc447fde4e4d Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 18:17:05 -0700 Subject: [PATCH 18/44] fix Result constructor warnings --- include/common/result.hpp | 4 ++-- tests/Result.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 tests/Result.cpp diff --git a/include/common/result.hpp b/include/common/result.hpp index ccc70c9a..32260079 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -58,8 +58,8 @@ class Result { template requires std::constructible_from constexpr Result(U&& value) - : value(std::forward(value)), - error(std::monostate()) {} + : error(std::monostate()), + value(std::forward(value)) {} // Construct a Result with a value and an error value. // Constraint: type T can be constructed from type U diff --git a/tests/Result.cpp b/tests/Result.cpp new file mode 100644 index 00000000..9edd2af8 --- /dev/null +++ b/tests/Result.cpp @@ -0,0 +1,7 @@ +#include "common/result.hpp" + +class MyError : public zest::ResultError {}; + +void initialize() { + zest::Result res(2); +} From 67f6c8a7278a00cdd78c1dce75923b26fd87528b Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 18:25:23 -0700 Subject: [PATCH 19/44] add comparison operator overload to Result --- include/common/result.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/common/result.hpp b/include/common/result.hpp index 32260079..eb94118e 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -138,6 +138,11 @@ class Result { return std::move(value); } + // comparison operator overload + constexpr bool operator==(const Result& other) { + return value == other.value; + } + // a variant that could contain any of the specified error types std::variant error; // the normal value From 99398cf0d9948e986497952aa470ed264f21a3a9 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 20:02:00 -0700 Subject: [PATCH 20/44] improve Result comparison operator --- include/common/result.hpp | 4 +++- tests/Result.cpp | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index eb94118e..caa331fc 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -139,7 +139,9 @@ class Result { } // comparison operator overload - constexpr bool operator==(const Result& other) { + template + requires std::equality_comparable_with + constexpr bool operator==(const Result& other) { return value == other.value; } diff --git a/tests/Result.cpp b/tests/Result.cpp index 9edd2af8..c9f50a69 100644 --- a/tests/Result.cpp +++ b/tests/Result.cpp @@ -2,6 +2,14 @@ class MyError : public zest::ResultError {}; +class MyError2 : public zest::ResultError {}; + void initialize() { - zest::Result res(2); + constexpr zest::Result a(2); + static_assert(a == 2); + static_assert(a == a); + static_assert(a == a.get()); + static_assert(a.get() == a.get()); + constexpr zest::Result b(2); + static_assert(a == b); } From dc99edf46f15f5790e13f7f0ae6103195b9f6a9c Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 20:12:03 -0700 Subject: [PATCH 21/44] fix SentinelValue specialization for integral types --- include/common/result.hpp | 1 + tests/Result.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/include/common/result.hpp b/include/common/result.hpp index caa331fc..eb06a4ab 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -40,6 +40,7 @@ constexpr T sentinel_v = SentinelValue::value; // any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value template class SentinelValue { + public: static constexpr T value = std::numeric_limits::max(); }; diff --git a/tests/Result.cpp b/tests/Result.cpp index c9f50a69..480e0838 100644 --- a/tests/Result.cpp +++ b/tests/Result.cpp @@ -4,6 +4,11 @@ class MyError : public zest::ResultError {}; class MyError2 : public zest::ResultError {}; +class IntError : public zest::ResultError { + public: + IntError(int) {} +}; + void initialize() { constexpr zest::Result a(2); static_assert(a == 2); @@ -12,4 +17,7 @@ void initialize() { static_assert(a.get() == a.get()); constexpr zest::Result b(2); static_assert(a == b); + + zest::Result c(2); + zest::Result d(IntError(2)); } From 41c3d29f81fcbc34585787a5c640ded840edd537 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 20:23:32 -0700 Subject: [PATCH 22/44] fix Result ctor order warnings --- include/common/result.hpp | 8 ++++---- tests/Result.cpp | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index eb06a4ab..5bb16f1d 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -79,8 +79,8 @@ class Result { requires Sentinel && (std::constructible_from || ...) && (!std::convertible_to) constexpr Result(U&& error) - : value(sentinel_v), - error(std::forward(error)) {} + : error(std::forward(error)), + value(sentinel_v) {} // Construct a Result with an error, initializing the normal value to its sentinel value. // Constraint: type T has a sentinel value @@ -88,8 +88,8 @@ class Result { template requires Sentinel && (std::constructible_from || ...) explicit constexpr Result(U&& error) - : value(sentinel_v), - error(std::forward(error)) {} + : error(std::forward(error)), + value(sentinel_v) {} // Get the given error type, if it exists // Constraint: some type Errs can be constructed from type U diff --git a/tests/Result.cpp b/tests/Result.cpp index 480e0838..a61cd157 100644 --- a/tests/Result.cpp +++ b/tests/Result.cpp @@ -17,7 +17,5 @@ void initialize() { static_assert(a.get() == a.get()); constexpr zest::Result b(2); static_assert(a == b); - - zest::Result c(2); - zest::Result d(IntError(2)); + auto c = a.get(); } From 7d4eea4dcb9ec2b83272af606a598a35461fda19 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Mon, 28 Apr 2025 20:43:31 -0700 Subject: [PATCH 23/44] add time point to ResultError --- include/common/result.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 5bb16f1d..4cfcabe2 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -15,9 +16,11 @@ class ResultError { // since this constructor has no arguments, it'll be called implicitly by the constructor of any // child class. ResultError() - : stacktrace(std::stacktrace::current()) {} + : stacktrace(std::stacktrace::current()), + time(std::chrono::system_clock::now()) {} std::stacktrace stacktrace; + std::chrono::time_point time; }; // Some primitive types such as float, double, etc may have a standardized "sentinel" value. From 3628c58195fe15aa758a48c1718e16ddd2f803b2 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Tue, 29 Apr 2025 20:20:56 -0700 Subject: [PATCH 24/44] improve Result error type constraints --- include/common/result.hpp | 87 ++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 4cfcabe2..718c0a55 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -53,6 +53,8 @@ class SentinelValue { // 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. +// Constraint: requires at least 1 possible Error type +// Constraint: Error types must inherit from ResultError template requires(sizeof...(Errs) > 0) && (std::derived_from && ...) class Result { @@ -67,56 +69,45 @@ class Result { // Construct a Result with a value and an error value. // Constraint: type T can be constructed from type U - // Constraint: some type in Errs can be constructed from type E + // Constraint: E in Errs template - requires std::constructible_from && (std::constructible_from || ...) + requires std::constructible_from && (std::same_as, Errs> || ...) constexpr Result(U&& value, E&& error) : value(std::forward(value)), error(std::forward(error)) {} // Construct a Result with an error, initializing the normal value to its sentinel value. // Constraint: type T has a sentinel value - // Constraint: some type in Errs can be constructed from type U - // Constraint: type U can't be implicitly converted to type T - template - requires Sentinel && (std::constructible_from || ...) - && (!std::convertible_to) - constexpr Result(U&& error) - : error(std::forward(error)), - value(sentinel_v) {} - - // Construct a Result with an error, initializing the normal value to its sentinel value. - // Constraint: type T has a sentinel value - // Constraint: some type in Errs can be constructed from type U - template - requires Sentinel && (std::constructible_from || ...) - explicit constexpr Result(U&& error) - : error(std::forward(error)), + // Constraint: E in Errs + template + requires Sentinel && (std::same_as, Errs> || ...) + constexpr Result(E&& error) + : error(std::forward(error)), value(sentinel_v) {} // Get the given error type, if it exists - // Constraint: some type Errs can be constructed from type U - template - requires(std::same_as || ...) - constexpr std::optional get() const& { + // Constraint: E in Errs + template + requires(std::same_as || ...) + constexpr std::optional get() const& { if (std::holds_alternative(error)) { return std::nullopt; } else { - return std::get(error); + return std::get(error); } - }; + } // Get the given error type, if it exists - // Constraint: some type Errs can be constructed from type U - template - requires(std::same_as || ...) - constexpr std::optional get() && { + // Constraint: E in Errs + template + requires(std::same_as || ...) + constexpr std::optional get() && { if (std::holds_alternative(error)) { return std::nullopt; } else { - return std::move(std::get(error)); + return std::move(std::get(error)); } - }; + } // get the normal value template @@ -155,43 +146,47 @@ class Result { T value; }; +// Result specialization for a value of type void, which would otherwise be impossible. +// This specialization simply does not hold a "normal" value +// Constraint: requires at least 1 possible Error type +// Constraint: Error types must inherit from ResultError template requires(sizeof...(Errs) > 0) && (std::derived_from && ...) class Result { public: // construct with an error value - template - requires(std::constructible_from || ...) - constexpr Result(U&& error) - : error(std::forward(error)) {} + template + requires(std::same_as, Errs> || ...) + constexpr Result(E&& error) + : error(std::forward(error)) {} // construct with no error value constexpr Result() : error(std::monostate()) {} // Get the given error type, if it exists - // Constraint: some type Errs can be constructed from type U - template - requires(std::same_as || ...) - constexpr std::optional get() const& { + // Constraint: E in Errs + template + requires(std::same_as || ...) + constexpr std::optional get() const& { if (std::holds_alternative(error)) { return std::nullopt; } else { - return std::get(error); + return std::get(error); } - }; + } // Get the given error type, if it exists - // Constraint: some type Errs can be constructed from type U - template - requires(std::same_as || ...) - constexpr std::optional get() && { + // Constraint: E in Errs + template + requires(std::same_as || ...) + constexpr std::optional get() && { if (std::holds_alternative(error)) { return std::nullopt; } else { - return std::move(std::get(error)); + return std::move(std::get(error)); } - }; + } std::variant error; }; From d5a4999d590678ddc9aa3ce86e223c1a6fe3778e Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Wed, 30 Apr 2025 14:06:10 -0700 Subject: [PATCH 25/44] use infinity as a sentinel value if possible --- include/common/result.hpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 718c0a55..5678c85e 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -40,11 +40,20 @@ template constexpr T sentinel_v = SentinelValue::value; // partial specialization for HasSentinel. -// any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value +// any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value. +// floating-point numbers with infinity support have sentinel values equal to infinity. template class SentinelValue { public: - static constexpr T value = std::numeric_limits::max(); + static constexpr T get() { + if constexpr (std::numeric_limits::has_infinity) { + return std::numeric_limits::infinity(); + } else { + return std::numeric_limits::max(); + } + } + + static constexpr T value = get(); }; // Result class. From 479f1c4be0c27b8becf65b91311b0e6216515f19 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Wed, 30 Apr 2025 14:11:01 -0700 Subject: [PATCH 26/44] fix bad variant access in Result::get --- include/common/result.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 5678c85e..89089062 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -99,10 +99,10 @@ class Result { template requires(std::same_as || ...) constexpr std::optional get() const& { - if (std::holds_alternative(error)) { - return std::nullopt; - } else { + if (std::holds_alternative(error)) { return std::get(error); + } else { + return std::nullopt; } } @@ -111,10 +111,10 @@ class Result { template requires(std::same_as || ...) constexpr std::optional get() && { - if (std::holds_alternative(error)) { - return std::nullopt; - } else { + if (std::holds_alternative(error)) { return std::move(std::get(error)); + } else { + return std::nullopt; } } From 953874fd2c5acccfb8beede784350e892e5cbbf3 Mon Sep 17 00:00:00 2001 From: Liam Teale <111480281+SizzinSeal@users.noreply.github.com> Date: Thu, 1 May 2025 15:34:21 -0700 Subject: [PATCH 27/44] fix SentinelValue floating-point values Co-authored-by: Leo Riesenbach --- include/common/result.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 89089062..167d893a 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -42,7 +42,8 @@ constexpr T sentinel_v = SentinelValue::value; // partial specialization for HasSentinel. // any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value. // floating-point numbers with infinity support have sentinel values equal to infinity. -template +template + requires (std::integral || std::floating_point) class SentinelValue { public: static constexpr T get() { From c0408dc3d4ab3743267cbac9dc2add10252989b2 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Thu, 1 May 2025 15:53:02 -0700 Subject: [PATCH 28/44] use doxygen documentation in result.hpp --- include/common/result.hpp | 169 +++++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 58 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 167d893a..0f481e23 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -9,41 +9,53 @@ namespace zest { -// custom error types inherit from the ResultError class, which enforces that custom error types -// have some shared functionality. +/** + * @brief Base class for custom error types used in the Result class. + * @details Enforces stacktrace and timestamp functionality for derived error types. + */ class ResultError { public: - // since this constructor has no arguments, it'll be called implicitly by the constructor of any - // child class. + /** + * @brief Construct a new ResultError object. + * @details Captures the current stacktrace and system time automatically. + */ ResultError() : stacktrace(std::stacktrace::current()), time(std::chrono::system_clock::now()) {} - std::stacktrace stacktrace; - std::chrono::time_point time; + std::stacktrace stacktrace; ///< Captured stacktrace at error creation. + std::chrono::time_point time; ///< Timestamp of error creation. }; -// 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) +/** + * @brief Trait to define a "sentinel" value for types indicating an error state. + * @tparam T Type to provide a sentinel value for. + * @note Specialize this template for custom types if needed. + */ template class SentinelValue; -// concept which can be used to determine if a type has a sentinel value +/** + * @brief Concept to check if a type has a defined sentinel value. + * @tparam T Type to check. + */ template concept Sentinel = requires(const T& val) { SentinelValue::value; }; -// templated variable which can be used to simplify usage of HasSentinel +/** + * @brief Helper variable to simplify access to a type's sentinel value. + * @tparam T Type with a defined sentinel (must satisfy Sentinel concept). + */ template constexpr T sentinel_v = SentinelValue::value; -// partial specialization for HasSentinel. -// any integral type (e.g double, int, uint8, etc) has a sentinel value equal to its maximum value. -// floating-point numbers with infinity support have sentinel values equal to infinity. +/** + * @brief Partial specialization of SentinelValue for integral and floating-point types. + * @tparam T Integral or floating-point type. + * @details Uses infinity for floating-point types if available; otherwise uses max value. + */ template - requires (std::integral || std::floating_point) + requires(std::integral || std::floating_point) class SentinelValue { public: static constexpr T get() { @@ -54,49 +66,60 @@ class SentinelValue { } } - static constexpr T value = get(); + static constexpr T value = get(); ///< Precomputed sentinel value for type T. }; -// 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. -// Constraint: requires at least 1 possible Error type -// Constraint: Error types must inherit from ResultError +/** + * @brief Result class for expected value or error handling (similar to std::expected). + * @tparam T Type of the expected value. + * @tparam Errs List of possible error types (must inherit from ResultError). + * @note Errors are stored in a variant, and the value is always initialized. + */ template requires(sizeof...(Errs) > 0) && (std::derived_from && ...) class Result { public: - // Construct a Result with a value and no error value. - // Constraint: type T can be constructed from type U + /** + * @brief Construct a Result with a normal value (no error). + * @tparam U Type convertible to T. + * @param value Value to initialize the result with. + */ template requires std::constructible_from constexpr Result(U&& value) : error(std::monostate()), value(std::forward(value)) {} - // Construct a Result with a value and an error value. - // Constraint: type T can be constructed from type U - // Constraint: E in Errs + /** + * @brief Construct a Result with a value and an error. + * @tparam U Type convertible to T. + * @tparam E Error type (must be in Errs). + * @param value Value to store. + * @param error Error to store. + */ template requires std::constructible_from && (std::same_as, Errs> || ...) constexpr Result(U&& value, E&& error) : value(std::forward(value)), error(std::forward(error)) {} - // Construct a Result with an error, initializing the normal value to its sentinel value. - // Constraint: type T has a sentinel value - // Constraint: E in Errs + /** + * @brief Construct a Result with an error, initializing the value to its sentinel. + * @tparam E Error type (must be in Errs). + * @param error Error to store. + * @note Requires T to have a defined sentinel value (via SentinelValue). + */ template requires Sentinel && (std::same_as, Errs> || ...) constexpr Result(E&& error) : error(std::forward(error)), value(sentinel_v) {} - // Get the given error type, if it exists - // Constraint: E in Errs + /** + * @brief Get an error of type E if present (const-qualified overload). + * @tparam E Error type to retrieve. + * @return std::optional Contains the error if present; otherwise nullopt. + */ template requires(std::same_as || ...) constexpr std::optional get() const& { @@ -107,8 +130,11 @@ class Result { } } - // Get the given error type, if it exists - // Constraint: E in Errs + /** + * @brief Get an error of type E if present (rvalue overload). + * @tparam E Error type to retrieve. + * @return std::optional Contains the error if present; otherwise nullopt. + */ template requires(std::same_as || ...) constexpr std::optional get() && { @@ -119,63 +145,86 @@ class Result { } } - // get the normal value + /** + * @brief Get the stored value (const-qualified overload). + * @return T Copy of the stored value. + */ template requires std::same_as constexpr T get() const& { return value; } - // get the normal value + /** + * @brief Get the stored value (rvalue overload). + * @return T Moved value. + */ template requires std::same_as constexpr T get() && { return std::move(value); } - // implicit conversion operator + /** + * @brief Implicit conversion to the stored value (const-qualified). + */ constexpr operator T() const& { return value; }; - // implicit conversion operator + /** + * @brief Implicit conversion to the stored value (rvalue). + */ constexpr operator T() && { return std::move(value); } - // comparison operator overload + /** + * @brief Equality comparison operator. + * @tparam U Type of the other result's value. + * @tparam Es Other result's error types. + * @param other Result to compare with. + * @return true If values are equal. + */ template requires std::equality_comparable_with constexpr bool operator==(const Result& other) { return value == other.value; } - // a variant that could contain any of the specified error types - std::variant error; - // the normal value - T value; + std::variant error; ///< Variant holding an error or monostate. + T value; ///< The stored value (always initialized). }; -// Result specialization for a value of type void, which would otherwise be impossible. -// This specialization simply does not hold a "normal" value -// Constraint: requires at least 1 possible Error type -// Constraint: Error types must inherit from ResultError +/** + * @brief Result specialization for void value type (no stored value). + * @tparam Errs List of possible error types (must inherit from ResultError). + */ template requires(sizeof...(Errs) > 0) && (std::derived_from && ...) class Result { public: - // construct with an error value + /** + * @brief Construct a Result with an error. + * @tparam E Error type (must be in Errs). + * @param error Error to store. + */ template requires(std::same_as, Errs> || ...) constexpr Result(E&& error) : error(std::forward(error)) {} - // construct with no error value + /** + * @brief Construct a Result with no error (success state). + */ constexpr Result() : error(std::monostate()) {} - // Get the given error type, if it exists - // Constraint: E in Errs + /** + * @brief Get an error of type E if present (const-qualified overload). + * @tparam E Error type to retrieve. + * @return std::optional Contains the error if present; otherwise nullopt. + */ template requires(std::same_as || ...) constexpr std::optional get() const& { @@ -186,8 +235,11 @@ class Result { } } - // Get the given error type, if it exists - // Constraint: E in Errs + /** + * @brief Get an error of type E if present (rvalue overload). + * @tparam E Error type to retrieve. + * @return std::optional Contains the error if present; otherwise nullopt. + */ template requires(std::same_as || ...) constexpr std::optional get() && { @@ -198,6 +250,7 @@ class Result { } } - std::variant error; + std::variant error; ///< Variant holding an error or monostate. }; + } // namespace zest \ No newline at end of file From f3970808fa80d893cc5c8fcc433f80f0e93989d2 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Thu, 1 May 2025 16:24:49 -0700 Subject: [PATCH 29/44] use std::remove_cvref instead of std::remove_cvref_t in result.hpp --- include/common/result.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 0f481e23..f74876f5 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -98,7 +98,8 @@ class Result { * @param error Error to store. */ template - requires std::constructible_from && (std::same_as, Errs> || ...) + requires std::constructible_from + && (std::same_as, Errs> || ...) constexpr Result(U&& value, E&& error) : value(std::forward(value)), error(std::forward(error)) {} @@ -110,7 +111,7 @@ class Result { * @note Requires T to have a defined sentinel value (via SentinelValue). */ template - requires Sentinel && (std::same_as, Errs> || ...) + requires Sentinel && (std::same_as, Errs> || ...) constexpr Result(E&& error) : error(std::forward(error)), value(sentinel_v) {} @@ -210,7 +211,7 @@ class Result { * @param error Error to store. */ template - requires(std::same_as, Errs> || ...) + requires(std::same_as, Errs> || ...) constexpr Result(E&& error) : error(std::forward(error)) {} From 7d1792e8c35b1d1f62093d56ac4fdeebdc5be2c9 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Thu, 1 May 2025 17:09:03 -0700 Subject: [PATCH 30/44] fix ambiguous Result comparison operator --- include/common/result.hpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index f74876f5..cf57dff6 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -180,23 +180,29 @@ class Result { return std::move(value); } - /** - * @brief Equality comparison operator. - * @tparam U Type of the other result's value. - * @tparam Es Other result's error types. - * @param other Result to compare with. - * @return true If values are equal. - */ - template - requires std::equality_comparable_with - constexpr bool operator==(const Result& other) { - return value == other.value; - } - std::variant error; ///< Variant holding an error or monostate. T value; ///< The stored value (always initialized). }; +/** + * @brief compare Result instances with comparable normal values + * + * @tparam LhsT the normal value type of the left-hand side argument + * @tparam RhsT the normal value type of the right-hand side argument + * @tparam LhsErrs the error value types of the left-hand side argument + * @tparam RhsErrs the error value types of the right-hand side argument + * @param lhs the left-hand side of the expression + * @param rhs the right-hand side of the expression + * @return true if the values are equal + * @return false if the values are not equal + */ +template + requires std::equality_comparable_with +constexpr bool +operator==(const Result& lhs, const Result& rhs) { + return lhs.value == rhs.value; +} + /** * @brief Result specialization for void value type (no stored value). * @tparam Errs List of possible error types (must inherit from ResultError). From a930af2308710b9f01d594162ea869d1adb7e3f8 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Thu, 1 May 2025 17:16:33 -0700 Subject: [PATCH 31/44] add comparison operator tests for Result --- tests/Result.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/Result.cpp b/tests/Result.cpp index a61cd157..a295f4f8 100644 --- a/tests/Result.cpp +++ b/tests/Result.cpp @@ -1,21 +1,24 @@ #include "common/result.hpp" +#include +#include + class MyError : public zest::ResultError {}; class MyError2 : public zest::ResultError {}; -class IntError : public zest::ResultError { - public: - IntError(int) {} -}; +constexpr void compile_time_tests() { + // test sentinel values + static_assert(zest::sentinel_v == INT32_MAX); + static_assert(zest::sentinel_v == std::numeric_limits::infinity()); -void initialize() { - constexpr zest::Result a(2); - static_assert(a == 2); - static_assert(a == a); - static_assert(a == a.get()); - static_assert(a.get() == a.get()); - constexpr zest::Result b(2); - static_assert(a == b); - auto c = a.get(); + { + // test comparison operator + static_assert(zest::Result(2) == zest::Result(2)); + static_assert(zest::Result(3) == zest::Result(3)); + static_assert(zest::Result(0) == 0); + static_assert(0 == zest::Result(0)); + } } + +void runtime_tests() {} \ No newline at end of file From 7a868470f9077ca3ee195df88fe2a0c97ea6c141 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Thu, 1 May 2025 17:52:08 -0700 Subject: [PATCH 32/44] add Result void specialization test --- tests/Result.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Result.cpp b/tests/Result.cpp index a295f4f8..49ee14f0 100644 --- a/tests/Result.cpp +++ b/tests/Result.cpp @@ -7,6 +7,13 @@ class MyError : public zest::ResultError {}; class MyError2 : public zest::ResultError {}; +zest::Result test_function_1() { + // return nothing + return {}; + // return MyError + return MyError(); +} + constexpr void compile_time_tests() { // test sentinel values static_assert(zest::sentinel_v == INT32_MAX); From ce756f790aa3c99142154465b8063d3b265344e7 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 08:37:02 -0700 Subject: [PATCH 33/44] remove `constexpr` qualifiers for certain `Result` member functions --- include/common/result.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index cf57dff6..e9b568a1 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -100,7 +100,7 @@ class Result { template requires std::constructible_from && (std::same_as, Errs> || ...) - constexpr Result(U&& value, E&& error) + Result(U&& value, E&& error) : value(std::forward(value)), error(std::forward(error)) {} @@ -112,7 +112,7 @@ class Result { */ template requires Sentinel && (std::same_as, Errs> || ...) - constexpr Result(E&& error) + Result(E&& error) : error(std::forward(error)), value(sentinel_v) {} @@ -123,7 +123,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional get() const& { + std::optional get() const& { if (std::holds_alternative(error)) { return std::get(error); } else { @@ -138,7 +138,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional get() && { + std::optional get() && { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { From 11d3b0d50634683007fddf4f68d82db94f84b795 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 11:35:08 -0700 Subject: [PATCH 34/44] support compile-time ResultError --- include/common/result.hpp | 45 ++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index e9b568a1..73686805 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -11,20 +11,34 @@ namespace zest { /** * @brief Base class for custom error types used in the Result class. - * @details Enforces stacktrace and timestamp functionality for derived error types. + * */ class ResultError { public: + /** + * @brief struct containing data that can only be known at runtime. + * + */ + struct RuntimeData { + std::stacktrace stacktrace; + std::chrono::time_point time; + }; + /** * @brief Construct a new ResultError object. - * @details Captures the current stacktrace and system time automatically. + * + * @details Captures the current stacktrace and system time if called at runtime. */ - ResultError() - : stacktrace(std::stacktrace::current()), - time(std::chrono::system_clock::now()) {} + constexpr ResultError() { + if !consteval { + runtime_data = { + .stacktrace = std::stacktrace::current(), + .time = std::chrono::system_clock::now() + }; + } + } - std::stacktrace stacktrace; ///< Captured stacktrace at error creation. - std::chrono::time_point time; ///< Timestamp of error creation. + std::optional runtime_data; }; /** @@ -100,7 +114,7 @@ class Result { template requires std::constructible_from && (std::same_as, Errs> || ...) - Result(U&& value, E&& error) + constexpr Result(U&& value, E&& error) : value(std::forward(value)), error(std::forward(error)) {} @@ -112,7 +126,7 @@ class Result { */ template requires Sentinel && (std::same_as, Errs> || ...) - Result(E&& error) + constexpr Result(E&& error) : error(std::forward(error)), value(sentinel_v) {} @@ -123,7 +137,7 @@ class Result { */ template requires(std::same_as || ...) - std::optional get() const& { + constexpr std::optional get() const& { if (std::holds_alternative(error)) { return std::get(error); } else { @@ -138,7 +152,7 @@ class Result { */ template requires(std::same_as || ...) - std::optional get() && { + constexpr std::optional get() && { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -180,8 +194,13 @@ class Result { return std::move(value); } - std::variant error; ///< Variant holding an error or monostate. - T value; ///< The stored value (always initialized). + /** + * @brief error value + * @details instead of wrapping the variant in std::optional, it's more efficient to use + * std::monostate. since we have to use std::variant in any case. + */ + std::variant error; + T value; }; /** From 5f1ff0bfdc2d27de0eae4e27859957f04f46770f Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 12:50:16 -0700 Subject: [PATCH 35/44] use proper move semantics for Result conversion operators --- include/common/result.hpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 73686805..e18393c1 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -180,17 +180,23 @@ class Result { return std::move(value); } - /** - * @brief Implicit conversion to the stored value (const-qualified). - */ - constexpr operator T() const& { + constexpr operator T() { return value; + } + + constexpr operator T&() & { + return &value; + } + + constexpr operator const T&() const& { + return &value; }; - /** - * @brief Implicit conversion to the stored value (rvalue). - */ - constexpr operator T() && { + constexpr operator T&&() && { + return std::move(value); + } + + constexpr operator const T&&() const&& { return std::move(value); } From d41d7662a15efc2b670c589696b0ca3abeebdf51 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 13:11:13 -0700 Subject: [PATCH 36/44] remove Result conversion operators returning r-value references --- include/common/result.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index e18393c1..476268d5 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -192,14 +192,6 @@ class Result { return &value; }; - constexpr operator T&&() && { - return std::move(value); - } - - constexpr operator const T&&() const&& { - return std::move(value); - } - /** * @brief error value * @details instead of wrapping the variant in std::optional, it's more efficient to use From 255afd483a9cfa8d5cce8b92c0db9e7419c5d75b Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 14:03:30 -0700 Subject: [PATCH 37/44] fix Result conversion operator qualifiers again --- include/common/result.hpp | 16 ++++++++++------ tests/Result.cpp | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 476268d5..19e487a4 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -180,18 +180,22 @@ class Result { return std::move(value); } - constexpr operator T() { - return value; - } - constexpr operator T&() & { - return &value; + return value; } constexpr operator const T&() const& { - return &value; + return value; }; + constexpr operator T&&() && { + return std::move(value); + } + + constexpr operator const T&&() const&& { + return std::move(value); + } + /** * @brief error value * @details instead of wrapping the variant in std::optional, it's more efficient to use diff --git a/tests/Result.cpp b/tests/Result.cpp index 49ee14f0..d9c4f9a4 100644 --- a/tests/Result.cpp +++ b/tests/Result.cpp @@ -3,6 +3,9 @@ #include #include +// there'll be a lot of unused variables, since we just want to see if it compiles +#pragma GCC diagnostic ignored "-Wunused-variable" + class MyError : public zest::ResultError {}; class MyError2 : public zest::ResultError {}; @@ -25,7 +28,19 @@ constexpr void compile_time_tests() { static_assert(zest::Result(3) == zest::Result(3)); static_assert(zest::Result(0) == 0); static_assert(0 == zest::Result(0)); + static_assert(0.0 == zest::Result(0)); + static_assert(zest::Result(0) == 0.0); + } + + { + // test conversion operators + zest::Result a = 2; + int& b = a; + const int& c = a; + int&& d = zest::Result(2); + const int&& e = zest::Result(2); + int f = a; } } -void runtime_tests() {} \ No newline at end of file +void runtime_tests() {} From b329187c1672c01a52837c54974ef377fbd95b64 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 15:44:10 -0700 Subject: [PATCH 38/44] add const-rvalue reference overload for Result::get --- include/common/result.hpp | 40 ++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 19e487a4..fec7cf3c 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -160,6 +160,21 @@ class Result { } } + /** + * @brief Get an error of type E if present (const rvalue overload). + * @tparam E Error type to retrieve. + * @return std::optional Contains the error if present; otherwise nullopt. + */ + template + requires(std::same_as || ...) + constexpr std::optional get() const&& { + if (std::holds_alternative(error)) { + return std::move(std::get(error)); + } else { + return std::nullopt; + } + } + /** * @brief Get the stored value (const-qualified overload). * @return T Copy of the stored value. @@ -256,10 +271,10 @@ class Result { template requires(std::same_as || ...) constexpr std::optional get() const& { - if (std::holds_alternative(error)) { - return std::nullopt; - } else { + if (std::holds_alternative(error)) { return std::get(error); + } else { + return std::nullopt; } } @@ -271,10 +286,25 @@ class Result { template requires(std::same_as || ...) constexpr std::optional get() && { - if (std::holds_alternative(error)) { - return std::nullopt; + if (std::holds_alternative(error)) { + return std::move(std::get(error)); } else { + return std::nullopt; + } + } + + /** + * @brief Get an error of type E if present (const rvalue overload). + * @tparam E Error type to retrieve. + * @return std::optional Contains the error if present; otherwise nullopt. + */ + template + requires(std::same_as || ...) + constexpr std::optional get() const&& { + if (std::holds_alternative(error)) { return std::move(std::get(error)); + } else { + return std::nullopt; } } From 29c8fa689526b94d82fcf1b1c46ff7ea796574c3 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 16:27:27 -0700 Subject: [PATCH 39/44] rename Result.cpp to result.cpp --- tests/{Result.cpp => result.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{Result.cpp => result.cpp} (100%) diff --git a/tests/Result.cpp b/tests/result.cpp similarity index 100% rename from tests/Result.cpp rename to tests/result.cpp From 36f16db4a290410e1d33d2abb999451670a6039a Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 19:09:29 -0700 Subject: [PATCH 40/44] fix Result::get return types --- include/common/result.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index fec7cf3c..44c0cd3d 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -152,7 +152,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional get() && { + constexpr std::optional&& get() && { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -167,7 +167,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional get() const&& { + constexpr const std::optional&& get() const&& { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -285,7 +285,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional get() && { + constexpr std::optional&& get() && { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -300,7 +300,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional get() const&& { + constexpr const std::optional&& get() const&& { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { From 44b326e1fcba48edb534ecf626104a9c754d9d65 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Fri, 2 May 2025 19:11:26 -0700 Subject: [PATCH 41/44] fix meson language server tweaking --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 824a91c1..c666f6a5 100644 --- a/meson.build +++ b/meson.build @@ -115,4 +115,4 @@ custom_target( input: elf, build_by_default: true, # otherwise it won't be built command: [objcopy, ['-O', 'binary', '-S', '@INPUT@', '@OUTPUT@']], -) \ No newline at end of file +) From 3a07636a374b09ef62b3327c946832e66f4dea74 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Sat, 3 May 2025 13:26:12 -0700 Subject: [PATCH 42/44] fix Result::get return type --- include/common/result.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/common/result.hpp b/include/common/result.hpp index 44c0cd3d..799c0c5b 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -152,7 +152,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional&& get() && { + constexpr std::optional get() && { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -167,7 +167,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr const std::optional&& get() const&& { + constexpr const std::optional get() const&& { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -285,7 +285,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr std::optional&& get() && { + constexpr std::optional get() && { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { @@ -300,7 +300,7 @@ class Result { */ template requires(std::same_as || ...) - constexpr const std::optional&& get() const&& { + constexpr const std::optional get() const&& { if (std::holds_alternative(error)) { return std::move(std::get(error)); } else { From b6a854aad3b268e7093d067b7f48510f0cd5f39c Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Sat, 3 May 2025 13:55:38 -0700 Subject: [PATCH 43/44] add more Result compile-time tests --- tests/result.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/result.cpp b/tests/result.cpp index d9c4f9a4..7fe53b3f 100644 --- a/tests/result.cpp +++ b/tests/result.cpp @@ -41,6 +41,14 @@ constexpr void compile_time_tests() { const int&& e = zest::Result(2); int f = a; } + + { + // test error getting + static_assert(zest::Result(MyError()).get()); + static_assert(!zest::Result(1).get()); + static_assert(zest::Result(1).get()); + static_assert(zest::Result(1).get()); + } } void runtime_tests() {} From 92bdad12f63ffcfc49718ef3a378d8745ac422e7 Mon Sep 17 00:00:00 2001 From: Liam Teale Date: Sat, 3 May 2025 13:57:23 -0700 Subject: [PATCH 44/44] add second Result test function --- tests/result.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/result.cpp b/tests/result.cpp index 7fe53b3f..e04378a4 100644 --- a/tests/result.cpp +++ b/tests/result.cpp @@ -17,6 +17,13 @@ zest::Result test_function_1() { return MyError(); } +zest::Result test_function_2() { + // return an integer + return 1; + // return MyError + return MyError(); +} + constexpr void compile_time_tests() { // test sentinel values static_assert(zest::sentinel_v == INT32_MAX);