libcruft-util/cruft/util/expected.hpp

179 lines
4.5 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include <exception>
#include <functional>
///////////////////////////////////////////////////////////////////////////////
namespace cruft {
class bad_expected_access : public std::exception {
public:
char const* what (void) const noexcept override
{
return "bad_expected_access";
}
};
template <typename ErrorT>
class unexpected {
public:
unexpected (ErrorT && _value)
noexcept (std::is_nothrow_constructible_v<ErrorT>)
: m_value (std::move (_value))
{ ; }
unexpected (ErrorT const &_value)
: m_value (_value)
{ ; }
ErrorT& value (void)& { return m_value; }
ErrorT&& value (void)&& { return std::move (m_value); }
ErrorT const& value (void) const & { return m_value; }
ErrorT const&& value (void) const&& { return std::move (m_value); }
private:
ErrorT m_value;
};
template <typename ValueT, typename ErrorT>
struct [[nodiscard]] expected {
using value_type = ValueT;
using error_type = ErrorT;
expected () = delete;
expected (expected &&rhs) noexcept (
std::is_nothrow_destructible_v<ValueT> &&
std::is_nothrow_destructible_v<ErrorT> &&
std::is_nothrow_move_constructible_v<ValueT> &&
std::is_nothrow_move_constructible_v<ErrorT>
) {
destroy ();
if (rhs) {
m_valid = true;
::new (&m_store.value) ValueT (std::move (rhs.value ()));
} else {
m_valid = false;
::new (&m_store.error) unexpected<ErrorT> (std::move (rhs.error ()));
}
}
expected& operator=(expected &&) noexcept (std::is_trivially_move_assignable_v<ValueT>);
expected (expected const&);
expected& operator=(expected const&);
expected (ValueT const &_value)
{
::new (&m_store.value) ValueT (_value);
m_valid = true;
}
expected (ValueT &&_value)
{
::new (&m_store.value) ValueT (std::move (_value));
m_valid = true;
}
expected (unexpected<ErrorT> &&_error)
{
::new (&m_store.error) unexpected<ErrorT> (std::move (_error));
m_valid = false;
}
~expected ()
{
destroy ();
}
ValueT&
value (void)&
{
if (!m_valid)
throw bad_expected_access {};
return m_store.value;
}
ValueT const&
value (void) const&
{
if (!m_valid)
throw bad_expected_access {};
return m_store.value;
}
ValueT&&
value (void)&&
{
if (!m_valid)
throw bad_expected_access {};
return std::move (m_store.value);
}
ErrorT&
error (void)&
{
if (m_valid)
throw bad_expected_access {};
return m_store.error.value ();
}
ErrorT const&
error (void) const&
{
if (m_valid) throw bad_expected_access {};
return m_store.error.value ();
}
ErrorT&&
error (void)&&
{
if (m_valid)
throw bad_expected_access {};
return std::move (m_store.error.value ());
}
ValueT* operator-> (void)& { return &value (); }
ValueT& operator* (void)& { return value (); }
ValueT const* operator-> (void) const& { return &value (); }
ValueT const& operator* (void) const& { return value (); }
bool has_value (void) const noexcept { return m_valid; }
explicit operator bool() const noexcept { return has_value (); }
private:
void destroy (void)
{
if (m_valid) {
m_store.value.~ValueT ();
m_valid = false;
} else {
m_store.error.~unexpected<ErrorT> ();
}
}
bool m_valid = false;
union storage_t {
storage_t () {};
~storage_t () {};
char defer;
ValueT value;
unexpected<ErrorT> error;
} m_store;
};
}