Last active
May 8, 2023 03:28
-
-
Save ugovaretto/bef31f987a962d50310dce22bc887374 to your computer and use it in GitHub Desktop.
Rust-style C++ Result implementation, supporting references
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Implementation of Result<Result,Error> type a la Rust. | |
// author: Ugo Varetto | |
// License: Zero-clause BSD | |
// SPDX identifier: 0BSD | |
#include "result.h" | |
//----------------------------------------------------------------------------- | |
Result<int, std::string> Foo(int i) { | |
if (i == 0) { | |
return Err(std::string("Error")); | |
} else { | |
return i; | |
} | |
} | |
const int I = 0; | |
int J = 0; | |
Result<const int &, std::string> FooCRef(int i) { | |
if (i <= 0) | |
return Err(std::string("Error: value <= 0")); | |
return I; | |
} | |
Result<int &, std::string> FooRef(int i) { | |
if (i <= 0) | |
return Err(std::string("Error: value <= 0")); | |
J = i; | |
return J; | |
} | |
Result<std::reference_wrapper<int>, std::string> FooR(int &i) { | |
if (i == 0) { | |
return Err(std::string("Error")); | |
} else { | |
return std::ref(i); | |
} | |
} | |
void ReceiveFoo(int x) { std::cout << x << std::endl; } | |
void ReceiveFooR(int &x) { std::cout << x << std::endl; } | |
void ReceiveFooCR(const int &x) { std::cout << x << std::endl; } | |
void ReceiveRRef(int &&x) { std::cout << x << std::endl; } | |
int main(int, char **) { | |
int n = 0; | |
if (auto r = FooR(n)) { | |
// int &rf = (int &)(r); | |
std::cout << r << std::endl; | |
} else { | |
std::cout << Error(r) << std::endl; | |
} | |
if (auto i = Foo(8)) { | |
std::cout << i << std::endl; | |
// Exit or exception thrown when accessing error | |
// in the presence of a valid result | |
// when DISABLE_ERROR_HANDLING NOT #defined | |
// stc::cout << Error(i) << std::endl; | |
} else { | |
// Exit or exception thrown when accessing result | |
// in the presence of error | |
// when DISABLE_ERROR_HANDLING NOT #defined | |
// std::cout << int(i) << std::endl; | |
std::cerr << Error(i) << std::endl; | |
} | |
int &r = FooRef(5); | |
std::cout << r << std::endl; | |
const int &cr = FooCRef(5); | |
std::cout << cr << std::endl; | |
ReceiveFoo((const int &)Foo(4)); | |
ReceiveFooR(FooRef(5)); | |
ReceiveFooCR(FooCRef(-7)); | |
ReceiveRRef(Foo(3)); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Implementation of Result<Result,Error> type a la Rust. | |
// author: Ugo Varetto | |
// License: Zero-clause BSD | |
// SPDX identifier: 0BSD | |
#include <functional> | |
#include <iostream> | |
#include <stdexcept> | |
#include <string> | |
#include <type_traits> | |
template <typename T> struct ErrorType { | |
T err; | |
ErrorType() = delete; | |
ErrorType(const T &e) : err(e) {} | |
ErrorType(T &&e) : err(std::move(e)) {} | |
}; | |
template <typename T> auto Err(T &&e) { return std::move(ErrorType(e)); } | |
// Error type requires to_string overload | |
inline std::string to_string(const std::string &s) { return s; } | |
//----------------------------------------------------------------------------- | |
// A union can have member functions (including constructors and destructors), | |
// but not virtual (10.3) functions. A union shall not have base classes. A | |
// union shall not be used as a base class. If a union contains a non-static | |
// data member of reference type the program is ill-formed. At most one | |
// non-static data member of a union may have a brace-or-equal-initializer . [ | |
// Note: If any non-static data member of a union has a non-trivial default | |
// constructor (12.1), copy constructor (12.8), move constructor (12.8), copy | |
// assignment operator (12.8), move assignment operator (12.8), or destructor | |
// (12.4), the corresponding member function of the union must be user-provided | |
// or it will be implicitly deleted (8.4.3) for the union. — end note ] | |
// i.e. A CUSTOM DESTRUCTOR TO CLEAN UP UNION ELEMENTS MUST BE PROVIDED | |
// | |
// A program is ill-formed if it instantiates an expected with a reference type, | |
// a function type, or a specialization of std::unexpected. In addition, T must | |
// not be std::in_place_t or std::unexpect_t i.e. C++23 DOES NOT SUPPORT | |
// expected WITH REFERENCES! | |
// | |
// This toy implementation supports references by using reference_wrapper<> | |
// in unions. | |
// const &, & and && are all supported for basic cases. volatile not handled | |
// explicitly. | |
// When DISABLE_ERROR_HANDLING is NOT #defined | |
// the default behaviour in case of access to result in the presence of error is | |
// exiting; can throw exception instead. | |
//============================================================================= | |
template <typename ErrT> struct ErrorHandler { | |
bool ok_ = false; | |
const ErrT &error_; | |
#ifndef DISABLE_ERROR_HANDLING | |
void ExitIfError(bool ok, const std::string &msg) const { | |
if (ok_ != ok) { | |
std::cerr << msg << " - " << to_string(error_) << std::endl; | |
exit(EXIT_FAILURE); | |
} | |
} | |
void ThrowIfError(bool ok, const std::string &msg) const { | |
if (ok_ != ok) { | |
throw std::logic_error(msg + " - " + to_string(error_)); | |
} | |
} | |
void HandleError(bool ok = true, | |
const std::string &msg = "Unchecked error condition") const { | |
#ifdef THROW_IF_ERROR | |
ThrowIfError(ok, msg); | |
#else | |
ExitIfError(ok, msg); | |
#endif | |
} | |
#endif | |
ErrorHandler(bool ok, const ErrT &err) : ok_(ok), error_(err) {} | |
bool Ok() const { return ok_; } | |
}; | |
//============================================================================= | |
// Result implementation | |
template <typename R, typename ErrT> class Result : private ErrorHandler<ErrT> { | |
using Base = ErrorHandler<ErrT>; | |
friend const ErrT &Error(const Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(false); // if ok != false segfaults | |
#endif | |
return r.error_; | |
} | |
friend const R &Value(const Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(); // if ok != false segfaults | |
#endif | |
return r.result_; | |
} | |
friend R &Value(Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(); // if ok != false segfaults | |
#endif | |
return r.result_; | |
} | |
friend R &&Value(Result &&r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(); // if ok != false segfaults | |
#endif | |
return std::move(r.result_); | |
} | |
private: | |
// union cannot have rerence type, either replace with | |
// struct increasing the size or wrap references | |
// with ref/cref | |
union { | |
R result_; | |
ErrT error_; | |
}; | |
public: | |
Result(Result &&r) : Base(r.ok_, error_) { | |
if (r.Ok()) { | |
result_ = std::move(r.result_); | |
} else { | |
error_ = std::move(r.error_); | |
} | |
} | |
Result(const Result &r) : Base(r.ok_, error_) { | |
if (r.Ok()) { | |
result_ = r.result_; | |
} else { | |
error_ = r.error_; | |
} | |
} | |
~Result() { | |
if (Base::Ok()) { | |
if constexpr (!std::is_trivially_destructible<R>::value) { | |
result_.~R(); | |
} | |
} else { | |
if constexpr (!std::is_trivially_destructible<ErrT>::value) { | |
error_.~ErrT(); | |
} | |
} | |
} | |
Result() = delete; | |
Result(ErrorType<ErrT> &&err) | |
: Base(false, error_), error_(std::move(err.err)) {} | |
Result(const R &r) : Base(true, error_), result_(r) {} | |
Result(R &&r) : Base(true, error_), result_(std::move(r)) {} | |
operator bool() const { return Base::Ok(); } | |
operator const R &() const { | |
#ifndef DISABLE_ERROR_HANDLING | |
Base::HandleError(); | |
#endif | |
return result_; | |
} | |
operator R &&() { | |
#ifndef DISABLE_ERROR_HANDLING | |
Base::HandleError(); | |
#endif | |
return std::move(result_); | |
} | |
}; | |
//============================================================================= | |
//----------------------------------------------------------------------------- | |
// Specialization for const reference -> reference_wrapper<const T> | |
template <typename R, typename ErrT> | |
class Result<const R &, ErrT> : private ErrorHandler<ErrT> { | |
using Base = ErrorHandler<ErrT>; | |
friend const ErrT &Error(const Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(false); // if ok != false segfaults | |
#endif | |
return r.error_; | |
} | |
friend const R &Value(const Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(); // if ok != false segfaults | |
#endif | |
return r.result_; | |
} | |
private: | |
union { | |
std::reference_wrapper<const R> result_; | |
ErrT error_; | |
}; | |
public: | |
Result(Result &&r) : Base(r.Ok(), error_) { | |
if (r.Ok()) { | |
result_ = std::move(r.result_); | |
} else { | |
error_ = std::move(r.error_); | |
} | |
} | |
Result(const Result &r) : Base(r.Ok(), error_) { | |
if (r.Ok()) { | |
result_ = r.result_; | |
} else { | |
error_ = r.error_; | |
} | |
} | |
~Result() { | |
if (Base::Ok()) { | |
if constexpr (!std::is_trivially_destructible<R>::value) { | |
result_.~R(); | |
} | |
} else { | |
if constexpr (!std::is_trivially_destructible<ErrT>::value) { | |
error_.~ErrT(); | |
} | |
} | |
} | |
Result() = delete; | |
Result(ErrorType<ErrT> &&err) | |
: Base(false, error_), error_(std::move(err.err)) {} | |
Result(const R &r) : Base(true, error_), result_(r) {} | |
Result(R &&r) : Base(true, error_), result_(std::move(r)) {} | |
operator bool() const { return Base::Ok(); } | |
operator const R &() const { | |
#ifndef DISABLE_ERROR_HANDLING | |
Base::HandleError(); | |
#endif | |
return result_; | |
} | |
operator const R &() { | |
#ifndef DISABLE_ERROR_HANDLING | |
Base::HandleError(); | |
#endif | |
return result_; | |
} | |
}; | |
//============================================================================= | |
//----------------------------------------------------------------------------- | |
// specialization for reference -> reference_wrapper<T> | |
template <typename R, typename ErrT> | |
class Result<R &, ErrT> : private ErrorHandler<ErrT> { | |
using Base = ErrorHandler<ErrT>; | |
template <typename R2, typename E2> | |
friend Result<R2, E2> ResultCast(const Result<R, ErrT> &); | |
template <typename R2, typename E2> | |
friend Result<R2, E2> ResultCast(Result<R, ErrT> &&); | |
friend const ErrT &Error(const Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(false); // if ok != false segfaults | |
#endif | |
return r.error_; | |
} | |
friend R &Value(const Result &r) { | |
#ifndef DISABLE_ERROR_HANDLING | |
r.HandleError(); // if ok != false segfaults | |
#endif | |
return r.result_; | |
} | |
private: | |
union { | |
std::reference_wrapper<R> result_; | |
ErrT error_; | |
}; | |
public: | |
Result(Result &&r) : Base(r.Ok()) { | |
if (r.Ok()) { | |
result_ = std::move(r.result_); | |
} else { | |
error_ = std::move(r.error_); | |
} | |
} | |
Result(const Result &r) : Base(r.Ok()) { | |
if (r.Ok()) { | |
result_ = r.result_; | |
} else { | |
error_ = r.error_; | |
} | |
} | |
~Result() { | |
if (Base::Ok()) { | |
if constexpr (!std::is_trivially_destructible<R>::value) { | |
result_.~R(); | |
} | |
} else { | |
if constexpr (!std::is_trivially_destructible<ErrT>::value) { | |
error_.~ErrT(); | |
} | |
} | |
} | |
Result() = delete; | |
Result(ErrorType<ErrT> &&err) | |
: Base(false, error_), error_(std::move(err.err)) {} | |
Result(R &r) : Base(true, error_), result_(r) {} | |
operator bool() const { return Base::Ok(); } | |
operator const R &() const { | |
#ifndef DISABLE_ERROR_HANDLING | |
Base::HandleError(); | |
#endif | |
return result_; | |
} | |
operator R &() { | |
#ifndef DISABLE_ERROR_HANDLING | |
Base::HandleError(); | |
#endif | |
return result_; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment