Refactor out optional and result

This commit is contained in:
Jeremy Rifkin 2025-01-26 13:30:44 -06:00
parent 1bcfcff021
commit 76a21d266a
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
4 changed files with 288 additions and 255 deletions

View File

@ -1,6 +1,7 @@
#ifndef ERROR_HPP
#define ERROR_HPP
#include <atomic>
#include <exception>
#include <string>
#include <utility>
@ -164,6 +165,8 @@ namespace detail {
// Check condition in both debug. std::runtime_error on failure.
#define ASSERT(...) PHONY_USE(__VA_ARGS__)
#endif
extern std::atomic_bool absorb_trace_exceptions;
}
}

154
src/utils/optional.hpp Normal file
View File

@ -0,0 +1,154 @@
#ifndef OPTIONAL_HPP
#define OPTIONAL_HPP
#include <new>
#include <type_traits>
#include <utility>
#include "utils/common.hpp"
#include "utils/error.hpp"
namespace cpptrace {
namespace detail {
struct nullopt_t {};
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
union {
char x;
T uvalue;
};
bool holds_value = false;
public:
optional() noexcept {}
optional(nullopt_t) noexcept {}
~optional() {
reset();
}
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
}
}
optional& operator=(const optional& other) {
optional copy(other);
swap(copy);
return *this;
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
optional o(std::forward<U>(value));
swap(o);
return *this;
}
optional& operator=(nullopt_t) noexcept {
reset();
return *this;
}
void swap(optional& other) noexcept {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) T(std::move(uvalue));
uvalue.~T();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
other.uvalue.~T();
}
std::swap(holds_value, other.holds_value);
}
bool has_value() const {
return holds_value;
}
explicit operator bool() const {
return holds_value;
}
void reset() {
if(holds_value) {
uvalue.~T();
}
holds_value = false;
}
NODISCARD T& unwrap() & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD const T& unwrap() const & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD T&& unwrap() && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
NODISCARD const T&& unwrap() const && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
};
}
}
#endif

129
src/utils/result.hpp Normal file
View File

@ -0,0 +1,129 @@
#ifndef RESULT_HPP
#define RESULT_HPP
#include <new>
#include <type_traits>
#include <utility>
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/optional.hpp"
namespace cpptrace {
namespace detail {
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
union {
T value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(T&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(T& value) : value_(T(value)), active(member::value) {}
Result(E& error) : error_(E(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) T(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~T();
} else {
error_.~E();
}
}
bool has_value() const {
return active == member::value;
}
bool is_error() const {
return active == member::error;
}
explicit operator bool() const {
return has_value();
}
NODISCARD optional<T> value() const & {
return has_value() ? value_ : nullopt;
}
NODISCARD optional<E> error() const & {
return is_error() ? error_ : nullopt;
}
NODISCARD optional<T> value() && {
return has_value() ? std::move(value_) : nullopt;
}
NODISCARD optional<E> error() && {
return is_error() ? std::move(error_) : nullopt;
}
NODISCARD T& unwrap_value() & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD const T& unwrap_value() const & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD T unwrap_value() && {
ASSERT(has_value(), "Result does not contain a value");
return std::move(value_);
}
NODISCARD E& unwrap_error() & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD const E& unwrap_error() const & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD E unwrap_error() && {
ASSERT(is_error(), "Result does not contain an error");
return std::move(error_);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
}
void drop_error() const {
if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
};
struct monostate {};
}
}
#endif

View File

@ -2,7 +2,6 @@
#define UTILS_HPP
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@ -16,6 +15,8 @@
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/optional.hpp"
#include "utils/result.hpp"
namespace cpptrace {
namespace detail {
@ -147,260 +148,6 @@ namespace detail {
static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result");
struct nullopt_t {};
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
union {
char x;
T uvalue;
};
bool holds_value = false;
public:
optional() noexcept {}
optional(nullopt_t) noexcept {}
~optional() {
reset();
}
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
}
}
optional& operator=(const optional& other) {
optional copy(other);
swap(copy);
return *this;
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
optional o(std::forward<U>(value));
swap(o);
return *this;
}
optional& operator=(nullopt_t) noexcept {
reset();
return *this;
}
void swap(optional& other) noexcept {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) T(std::move(uvalue));
uvalue.~T();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
other.uvalue.~T();
}
std::swap(holds_value, other.holds_value);
}
bool has_value() const {
return holds_value;
}
explicit operator bool() const {
return holds_value;
}
void reset() {
if(holds_value) {
uvalue.~T();
}
holds_value = false;
}
NODISCARD T& unwrap() & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD const T& unwrap() const & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD T&& unwrap() && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
NODISCARD const T&& unwrap() const && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
};
extern std::atomic_bool absorb_trace_exceptions;
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
union {
T value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(T&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(T& value) : value_(T(value)), active(member::value) {}
Result(E& error) : error_(E(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) T(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~T();
} else {
error_.~E();
}
}
bool has_value() const {
return active == member::value;
}
bool is_error() const {
return active == member::error;
}
explicit operator bool() const {
return has_value();
}
NODISCARD optional<T> value() const & {
return has_value() ? value_ : nullopt;
}
NODISCARD optional<E> error() const & {
return is_error() ? error_ : nullopt;
}
NODISCARD optional<T> value() && {
return has_value() ? std::move(value_) : nullopt;
}
NODISCARD optional<E> error() && {
return is_error() ? std::move(error_) : nullopt;
}
NODISCARD T& unwrap_value() & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD const T& unwrap_value() const & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD T unwrap_value() && {
ASSERT(has_value(), "Result does not contain a value");
return std::move(value_);
}
NODISCARD E& unwrap_error() & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD const E& unwrap_error() const & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD E unwrap_error() && {
ASSERT(is_error(), "Result does not contain an error");
return std::move(error_);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
}
void drop_error() const {
if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
};
struct monostate {};
// TODO: Re-evaluate use of off_t
template<typename T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0>
Result<T, internal_error> load_bytes(std::FILE* object_file, off_t offset) {