Skip to content

Instantly share code, notes, and snippets.

@glampert
Created January 7, 2018 20:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glampert/ce6fde15a742d467f0a61a6831153954 to your computer and use it in GitHub Desktop.
Save glampert/ce6fde15a742d467f0a61a6831153954 to your computer and use it in GitHub Desktop.
Mostly complete and usable C++ version of Rust's Option<T> and Result<T,E> using only standard C++14.
// Mostly complete and usable C++ version of Rust's Option<T> and Result<T,E> using only standard C++14, for fun.
// This code is in the public domain.
///////////////////////////////////////////////////////////////////////////////
// Option.hpp
///////////////////////////////////////////////////////////////////////////////
#pragma once
// ----------------------------------------------------------------------------
// Includes
// ----------------------------------------------------------------------------
#include <cstdio>
#include <string>
#include <functional>
#include <type_traits>
#include <utility>
// ----------------------------------------------------------------------------
// detail
// ----------------------------------------------------------------------------
namespace detail {
[[noreturn]]
inline void option_error(const char* const error_msg) noexcept {
std::fprintf(stderr, "Option<T> error: %s\n", error_msg);
std::abort();
}
struct NoneType final {};
} // detail
// ----------------------------------------------------------------------------
// 'None' constant
// ----------------------------------------------------------------------------
constexpr detail::NoneType None;
// ----------------------------------------------------------------------------
// Option<T>
// ----------------------------------------------------------------------------
template<typename T>
class Option final {
public:
static constexpr bool IsHoldingReference = std::is_reference<T>::value;
using ValueReference = std::reference_wrapper<typename std::remove_reference<T>::type>;
using Value = typename std::conditional<IsHoldingReference, ValueReference, T>::type;
/*implicit*/ Option(detail::NoneType) noexcept
: m_state{State::IsNone} {
}
explicit Option(const Value& value) noexcept(std::is_nothrow_copy_constructible<Value>::value)
: m_state{State::IsSome} {
::new(&some()) Value(value);
}
explicit Option(Value&& value) noexcept(std::is_nothrow_move_constructible<Value>::value)
: m_state{State::IsSome} {
::new(&some()) Value(std::forward<Value>(value));
}
/*implicit*/ Option(const Option& other) noexcept(std::is_nothrow_copy_constructible<Value>::value)
: m_state{other.m_state} {
if (other.is_some()) {
::new(&some()) Value(other.some());
}
}
/*implicit*/ Option(Option&& other) noexcept(std::is_nothrow_move_constructible<Value>::value)
: m_state{other.m_state} {
if (other.is_some()) {
::new(&some()) Value(std::forward<Value>(other.some()));
}
}
Option& operator=(const Option& other) noexcept(std::is_nothrow_constructible<Value>::value &&
std::is_nothrow_destructible<Value>::value) {
destroy_some();
m_state = other.m_state;
if (other.is_some()) {
::new(&some()) Value(other.some());
}
return *this;
}
Option& operator=(Option&& other) noexcept(std::is_nothrow_move_constructible<Value>::value &&
std::is_nothrow_destructible<Value>::value) {
destroy_some();
m_state = other.m_state;
if (other.is_some()) {
::new(&some()) Value(std::forward<Value>(other.some()));
}
return *this;
}
~Option() noexcept(std::is_nothrow_destructible<Value>::value) {
destroy_some();
}
bool is_some() const noexcept {
return m_state == State::IsSome;
}
bool is_none() const noexcept {
return m_state == State::IsNone;
}
bool operator==(detail::NoneType) const noexcept {
return is_none();
}
bool operator!=(detail::NoneType) const noexcept {
return !is_none();
}
Option<const T&> as_ref() const noexcept {
if (is_some()) {
return Option<const T&>(some());
} else {
return None;
}
}
Option<T&> as_mut() noexcept {
if (is_some()) {
return Option<T&>(some());
} else {
return None;
}
}
Value& expect(const char* const error_msg) noexcept {
if (is_none()) {
detail::option_error(error_msg);
}
return some();
}
const Value& expect(const char* const error_msg) const noexcept {
if (is_none()) {
detail::option_error(error_msg);
}
return some();
}
Value& expect(const std::string& error_msg) noexcept {
return expect(error_msg.c_str());
}
const Value& expect(const std::string& error_msg) const noexcept {
return expect(error_msg.c_str());
}
Value& unwrap() noexcept {
return expect("Called Option::unwrap() on a None value");
}
const Value& unwrap() const noexcept {
return expect("Called Option::unwrap() on a None value");
}
Value unwrap_or(const Value& def) const {
if (is_some()) {
return some();
} else {
return def;
}
}
template<typename F>
Value unwrap_or_else(F&& fn) const {
if (is_some()) {
return some();
} else {
return fn();
}
}
Value unwrap_or_default() const {
if (is_some()) {
return some();
} else {
return Value{};
}
}
template<typename F, typename U = std::result_of<F(const Value&)>::type>
Option<U> map(F&& fn) const {
if (is_some()) {
return Option<U>(fn(some()));
} else {
return None;
}
}
template<typename U, typename F>
U map_or(const U& def, F&& fn) const {
if (is_some()) {
return fn(some());
} else {
return def;
}
}
template<typename FDefault, typename F>
auto map_or_else(FDefault&& def_fn, F&& fn) const {
if (is_some()) {
return fn(some());
} else {
return def_fn();
}
}
template<typename U>
Option<U> and(const Option<U>& optb) const {
if (is_some()) {
return optb;
} else {
return None;
}
}
template<typename F, typename U = std::result_of<F(const Value&)>::type>
Option<U> and_then(F&& fn) const {
if (is_some()) {
return Option<U>(fn(some()));
} else {
return None;
}
}
Option<Value> or(const Option<Value>& optb) const {
if (is_some()) {
return Option<Value>(some());
} else {
return optb;
}
}
template<typename F>
Option<Value> or_else(F&& fn) const {
if (is_some()) {
return Option<Value>(some());
} else {
return fn();
}
}
Value& get_or_insert(const Value& value) {
if (is_none()) {
*this = Option(value);
}
return some();
}
template<typename F>
Value& get_or_insert_with(F&& fn) {
if (is_none()) {
*this = Option(fn());
}
return some();
}
Option<Value> take() {
auto copy{*this};
*this = None;
return copy;
}
private:
const Value& some() const noexcept { return m_some.m_value; }
Value& some() noexcept { return m_some.m_value; }
void destroy_some() noexcept(std::is_nothrow_destructible<Value>::value) {
if (is_some() && !std::is_trivially_destructible<Value>::value) {
m_some.m_value.~Value();
}
}
union Storage {
Storage() {}
~Storage() {}
Value m_value;
};
enum class State : char {
IsNone,
IsSome,
};
Storage m_some;
State m_state;
};
// ----------------------------------------------------------------------------
// Some()
// ----------------------------------------------------------------------------
template<typename T>
inline auto Some(T value) {
return Option<T>(std::move(value));
}
///////////////////////////////////////////////////////////////////////////////
// Result.hpp
///////////////////////////////////////////////////////////////////////////////
#pragma once
// ----------------------------------------------------------------------------
// Includes
// ----------------------------------------------------------------------------
#include "Option.hpp"
#include <cstdio>
#include <string>
#include <functional>
#include <type_traits>
#include <utility>
// ----------------------------------------------------------------------------
// detail
// ----------------------------------------------------------------------------
#if (__cplusplus >= 201703L)
#define RESULT_WARN_UNUSED [[nodiscard]]
#else // Not C++17
#if defined(_MSC_VER)
#include <sal.h>
#define RESULT_WARN_UNUSED _Check_return_
#elif defined(__GNUC__)
#define RESULT_WARN_UNUSED __attribute__((warn_unused_result))
#else
#define RESULT_WARN_UNUSED /*nothing*/
#endif
#endif
namespace detail {
[[noreturn]]
inline void result_error(const char* const error_msg) noexcept {
std::fprintf(stderr, "Result<T,E> error: %s\n", error_msg);
std::abort();
}
struct ResultValuePlaceholder final {};
struct ResultErrorPlaceholder final {};
} // detail
// ----------------------------------------------------------------------------
// Result<T,E>
// ----------------------------------------------------------------------------
template<typename T, typename E>
class RESULT_WARN_UNUSED Result final {
public:
static constexpr bool IsHoldingValueReference = std::is_reference<T>::value;
static constexpr bool IsHoldingErrorReference = std::is_reference<E>::value;
using ValueReference = std::reference_wrapper<typename std::remove_reference<T>::type>;
using ErrorReference = std::reference_wrapper<typename std::remove_reference<E>::type>;
using Value = typename std::conditional<IsHoldingValueReference, ValueReference, T>::type;
using Error = typename std::conditional<IsHoldingErrorReference, ErrorReference, E>::type;
template<typename V2>
/*implicit*/ Result(const Result<V2, detail::ResultErrorPlaceholder>& val_holder)
: Result(val_holder.unwrap()) {
}
template<typename E2>
/*implicit*/ Result(const Result<detail::ResultValuePlaceholder, E2>& err_holder)
: Result(err_holder.unwrap_err()) {
}
explicit Result(const Value& val) {
::new(&m_data.as_value) Value(val);
m_is_err = false;
}
explicit Result(Value&& val) {
::new(&m_data.as_value) Value(std::forward<Value>(val));
m_is_err = false;
}
explicit Result(const Error& err) {
::new(&m_data.as_error) Error(err);
m_is_err = true;
}
explicit Result(Error&& err) {
::new(&m_data.as_error) Error(std::forward<Error>(err));
m_is_err = true;
}
Result(const Result& other) {
copy_helper(other);
}
Result(Result&& other) {
move_helper(std::forward<Result>(other));
}
Result& operator=(const Result& other) {
dtor_helper();
copy_helper(other);
return *this;
}
Result& operator=(Result&& other) {
dtor_helper();
move_helper(std::forward<Result>(other));
return *this;
}
~Result() {
dtor_helper();
}
bool is_ok() const noexcept {
return !is_err();
}
bool is_err() const noexcept {
return m_is_err;
}
const Value& unwrap() const noexcept {
if (is_err()) {
detail::result_error("Called Result::unwrap() on an Error");
}
return m_data.as_value;
}
Value& unwrap() noexcept {
if (is_err()) {
detail::result_error("Called Result::unwrap() on an Error");
}
return m_data.as_value;
}
const Error& unwrap_err() const noexcept {
if (is_ok()) {
detail::result_error("Called Result::unwrap_err() on a Value");
}
return m_data.as_error;
}
Error& unwrap_err() noexcept {
if (is_ok()) {
detail::result_error("Called Result::unwrap_err() on a Value");
}
return m_data.as_error;
}
Option<Value> ok() const {
if (is_ok()) {
return Some(m_data.as_value);
} else {
return None;
}
}
Option<Error> err() const {
if (is_err()) {
return Some(m_data.as_error);
} else {
return None;
}
}
Result<const T&, const E&> as_ref() const noexcept {
if (is_ok()) {
return Result<const T&, const E&>(m_data.as_value);
} else {
return Result<const T&, const E&>(m_data.as_error);
}
}
Result<T&, E&> as_mut() noexcept {
if (is_ok()) {
return Result<T&, E&>(m_data.as_value);
} else {
return Result<T&, E&>(m_data.as_error);
}
}
private:
void dtor_helper() {
if (is_ok()) {
if (!std::is_trivially_destructible<Value>::value) {
m_data.as_value.~Value();
}
} else {
if (!std::is_trivially_destructible<Error>::value) {
m_data.as_error.~Error();
}
}
}
void copy_helper(const Result& other) {
if (other.is_ok()) {
::new(&m_data.as_value) Value(other.m_data.as_value);
} else {
::new(&m_data.as_error) Error(other.m_data.as_error);
}
m_is_err = other.m_is_err;
}
void move_helper(Result&& other) {
if (other.is_ok()) {
::new(&m_data.as_value) Value(std::move(other.m_data.as_value));
} else {
::new(&m_data.as_error) Error(std::move(other.m_data.as_error));
}
m_is_err = other.m_is_err;
}
private:
union Data {
Data() {}
~Data() {}
Value as_value;
Error as_error;
};
Data m_data;
bool m_is_err;
};
// ----------------------------------------------------------------------------
// Ok(), Err()
// ----------------------------------------------------------------------------
template<typename T>
inline auto Ok(T value) {
return Result<T, detail::ResultErrorPlaceholder>(std::move(value));
}
template<typename E>
inline auto Err(E error) {
return Result<detail::ResultValuePlaceholder, E>(std::move(error));
}
///////////////////////////////////////////////////////////////////////////////
// Tests.cpp
///////////////////////////////////////////////////////////////////////////////
#include "Option.hpp"
#include "Result.hpp"
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <string>
using namespace std::string_literals;
// ----------------------------------------------------------------------------
// Tests
// ----------------------------------------------------------------------------
struct S1 {
S1() { std::printf("S1()\n"); }
~S1() { std::printf("~S1()\n"); }
S1(const S1&) { std::printf("S1(const S1&)\n"); }
S1(S1&&) { std::printf("S1(S1&&)\n"); }
S1& operator=(const S1&) { std::printf("operator=(const S1&)\n"); return *this; }
S1& operator=(S1&&) { std::printf("operator=(S1&&)\n"); return *this; }
};
struct S2 {
S2() { std::printf("S2()\n"); }
~S2() { std::printf("~S2()\n"); }
S2(const S2&) { std::printf("S2(const S2&)\n"); }
S2(S2&&) { std::printf("S2(S2&&)\n"); }
S2& operator=(const S2&) { std::printf("operator=(const S2&)\n"); return *this; }
S2& operator=(S2&&) { std::printf("operator=(S2&&)\n"); return *this; }
};
// ----------------------------------------------------------------------------
namespace test_optional {
Option<S1> get_some() {
auto r = S1{};
return Some(r);
}
Option<S2> get_none() {
return None;
}
Option<double> divide(double numerator, double denominator) {
if (denominator == 0.0) {
return None;
} else {
return Some(numerator / denominator);
}
}
void run() {
auto a = get_some();
auto b = get_none();
Option<S1> n = None;
assert(a.is_some());
assert(b.is_none());
assert(n.is_none());
Option<const S1 &> ref_a = a.as_ref();
Option<S2 &> m_ref_b = b.as_mut();
assert(ref_a.is_some());
assert(m_ref_b.is_none());
Option<int> no_int = None;
Option<int> some_int = Some(42);
assert(no_int == None);
assert(no_int.is_none());
assert(some_int != None);
assert(some_int.is_some());
assert(some_int.unwrap() == 42);
auto air = Some("air"s);
assert(air.unwrap() == "air"s);
auto maybe_string = Some("value"s);
assert(maybe_string.expect("the world is ending"s) == "value"s);
assert(Some("car"s).unwrap_or("bike"s) == "car"s);
assert(Option<std::string>{None}.unwrap_or("bike"s) == "bike"s);
const int k1 = 10;
assert(Some(4).unwrap_or_else([k1]() { return 2 * k1; }) == 4);
assert(Option<int>{None}.unwrap_or_else([k1]() { return 2 * k1; }) == 20);
auto maybe_some_string = Some("Hello, World!"s);
auto maybe_some_len = maybe_some_string.map(
[](const std::string& s) { return unsigned(s.length()); });
assert(maybe_some_len.unwrap() == 13u);
auto d0 = divide(3.0, 2.0);
assert(d0.unwrap() == 1.5);
auto d1 = divide(3.0, 0.0);
assert(d1.is_none());
auto foo = Some("foo"s);
assert(foo.map_or(42u,
[](const std::string& s) { return unsigned(s.length()); }) == 3u);
Option<std::string> bar = None;
assert(bar.map_or(42u,
[](const std::string& s) { return unsigned(s.length()); }) == 42u);
const auto k2 = 21u;
auto x = Some("foo");
assert(x.map_or_else(
[k2]() { return 2u * k2; },
[](const std::string & s) { return unsigned(s.length()); }) == 3);
Option<std::string> y = None;
assert(y.map_or_else(
[k2]() { return 2u * k2; },
[](const std::string & s) { return unsigned(s.length()); }) == 42);
auto i = Some(123);
assert(i.unwrap_or_default() == 123);
Option<int> j = None;
assert(j.unwrap_or_default() == 0);
Option<unsigned> xy = None;
xy = None;
{
unsigned& zy = xy.get_or_insert(5u);
assert(zy == 5u);
zy = 7u;
}
assert(xy.unwrap() == 7u);
xy = None;
{
unsigned& zy = xy.get_or_insert_with([]() { return 5u; });
assert(zy == 5u);
zy = 7u;
}
assert(xy.unwrap() == 7u);
Option<int> xa = Some(2);
Option<int> old = xa.take();
assert(xa == None);
assert(old.unwrap() == 2);
Option<int> xb = None;
old = xb.take();
assert(xb == None);
assert(old.is_none());
Option<int> xx = Some(2);
Option<std::string> yy = None;
assert(xx.and(yy) == None);
auto zero = [](int) { return 0; };
auto square = [](int n) { return n*n; };
assert(Some(2).and_then(square).and_then(square).unwrap() == 16);
assert(Some(2).and_then(square).and_then(zero).unwrap() == 0);
assert(Some(2).and_then(zero).and_then(square).unwrap() == 0);
assert(Option<int>{None}.and_then(square).and_then(square) == None);
auto nobody = []() -> Option<std::string> { return None; };
auto vikings = []() -> Option<std::string> { return Some("vikings"s); };
assert(Some("barbarians"s).or_else(vikings).unwrap() == "barbarians"s);
assert(Option<std::string>{None}.or_else(vikings).unwrap() == "vikings"s);
assert(Option<std::string>{None}.or_else(nobody) == None);
}
} // test_optional
// ----------------------------------------------------------------------------
namespace test_result {
Result<S1, S2> get_result_ok() {
auto r = S1{};
return Ok(r);
}
Result<S1, S2> get_result_err() {
auto r = S2{};
return Err(r);
}
void run() {
auto o = get_result_ok();
auto e = get_result_err();
assert(o.is_ok());
assert(e.is_err());
Result<int, std::string> x = Ok(2);
assert(x.ok().unwrap() == 2);
Result<int, std::string> y = Err("Nothing here"s);
assert(y.ok() == None);
Result<int&, std::string&> xref = x.as_mut();
Result<const int&, const std::string&> yref = y.as_ref();
assert(xref.is_ok());
assert(yref.is_err());
}
} // test_result
// ----------------------------------------------------------------------------
int main() {
test_optional::run();
test_result::run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment