diff --git a/include/common/result.hpp b/include/common/result.hpp index bdc9f3a3..5706bcb0 100644 --- a/include/common/result.hpp +++ b/include/common/result.hpp @@ -1,328 +1,150 @@ #pragma once -#include #include -#include #include -#include -#include +#include +#include namespace zest { /** - * @brief Base class for custom error types used in the Result class. + * @brief Helper class for error handling. * + * @tparam T "normal" type + * @tparam E "error" type + * + * Constraints: + * - E must be a scoped enum + * - E must not be implicitly convertible to T + * - E must not be equality comparable with T */ -class ResultError { +template + requires std::is_scoped_enum_v && (!std::convertible_to) + && (!std::equality_comparable_with) +class Result { public: - /** - * @brief struct containing data that can only be known at runtime. - * - */ - struct RuntimeData { - std::stacktrace stacktrace; - std::chrono::time_point time; - }; + std::optional error; + T value; /** - * @brief Construct a new ResultError object. + * @brief default constructor * - * @details Captures the current stacktrace and system time if called at runtime. + * Constraints: + * - T must have a default constructor */ - constexpr ResultError() { - if !consteval { - runtime_data = { - .stacktrace = std::stacktrace::current(), - .time = std::chrono::system_clock::now() - }; - } - } - - std::optional runtime_data; -}; - -/** - * @brief Unknown Error - * - */ -class UnknownError : public ResultError { - public: - template - requires std::convertible_to - UnknownError(T&& message) - : message(std::forward(message)) {} - - std::string message; -}; - -/** - * @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; - -/** - * @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; }; - -/** - * @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; - -/** - * @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) -class SentinelValue { - public: - 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(); ///< Precomputed sentinel value for type T. -}; + constexpr Result() + requires std::default_initializable + = default; -/** - * @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: /** - * @brief Construct a Result with a normal value (no error). - * @tparam U Type convertible to T. - * @param value Value to initialize the result with. + * @brief Construct with a "normal" value, and no error value + * + * @tparam U argument type + * + * @param value argument to initialize the "normal" value with + * + * Constraints: + * - T must be constructible with perfectly forwarded value argument */ template - requires std::constructible_from + requires std::constructible_from constexpr Result(U&& value) - : error(std::monostate()), - value(std::forward(value)) {} - - /** - * @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)) {} + : value(std::forward(value)) {} /** - * @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) {} - - /** - * @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. + * @brief Construct with an "error" value, and the default "normal" value + * + * @param error argument to initialize the "error" value with + * + * Constraints: + * - T must be default initializable */ - template - requires(std::same_as || ...) - constexpr std::optional get() const& { - if (std::holds_alternative(error)) { - return std::get(error); - } else { - return std::nullopt; - } - } + constexpr Result(E error) + requires std::default_initializable + : error(error) {} /** - * @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. + * @brief Construct with an "error" value and "normal" value + * + * @tparam U value argument type + * + * @param error argument to initialize the "error" value with + * @param value argument to initialize the "normal" value with + * + * Constraints: + * - T must be constructible with perfectly forwarded value argument */ - template - requires(std::same_as || ...) - constexpr std::optional get() && { - if (std::holds_alternative(error)) { - return std::move(std::get(error)); - } else { - return std::nullopt; - } - } + template + requires std::constructible_from + constexpr Result(E error, U&& value) + : error(error), + value(std::forward(value)) {} /** - * @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. + * @brief conversion operator for an l-value reference to the "normal" value type + * + * @return T& */ - template - requires(std::same_as || ...) - constexpr const std::optional get() const&& { - if (std::holds_alternative(error)) { - return std::move(std::get(error)); - } else { - return std::nullopt; - } + constexpr operator T&() & { + return value; } /** - * @brief Get the stored value (const-qualified overload). - * @return T Copy of the stored value. + * @brief conversion operator for a const l-value reference to the "normal" value type + * + * @return const T& */ - template - requires std::same_as - constexpr T get() const& { + constexpr operator const T&() const& { return value; } /** - * @brief Get the stored value (rvalue overload). - * @return T Moved value. + * @brief conversion operator for an r-value reference to the "normal" value type + * + * @return T&& */ - template - requires std::same_as - constexpr T get() && { - return std::move(value); - } - - constexpr operator T&() & { - return value; - } - - constexpr operator const T&() const& { - 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 - * std::monostate. since we have to use std::variant in any case. - */ - std::variant error; - T value; -}; - -/** - * @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). - */ -template - requires(sizeof...(Errs) > 0) && (std::derived_from && ...) -class Result { - public: - /** - * @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)) {} - - /** - * @brief Construct a Result with no error (success state). - */ - constexpr Result() - : error(std::monostate()) {} - - /** - * @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. + * @brief Get the value object + * + * @tparam Self + * + * @param self + * + * @return "normal" value */ - template - requires(std::same_as || ...) - constexpr std::optional get() const& { - if (std::holds_alternative(error)) { - return std::get(error); - } else { - return std::nullopt; - } + template + constexpr auto get_value(this Self&& self) { + return std::forward(self).value; } /** - * @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. + * @brief Whether an error is contained + * + * @return true an error is contained + * @return false an error is not contained */ - template - requires(std::same_as || ...) - constexpr std::optional get() && { - if (std::holds_alternative(error)) { - return std::move(std::get(error)); - } else { - return std::nullopt; - } + constexpr bool has_error() { + return error.has_value(); } /** - * @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. + * @brief Whether a specific error is contained + * + * @param error the error to check + * + * @return true error is contained + * @return false error is not contained */ - template - requires(std::same_as || ...) - constexpr const std::optional get() const&& { - if (std::holds_alternative(error)) { - return std::move(std::get(error)); - } else { - return std::nullopt; - } + constexpr bool contains(E error) { + return this->error == error; } - std::variant error; ///< Variant holding an error or monostate. + // prevent ambiguous operator overload resolution + bool operator==(const Result& other) = delete; }; } // namespace zest \ No newline at end of file