Skip to content
Snippets Groups Projects
Unverified Commit 74e911e6 authored by Sam Yates's avatar Sam Yates Committed by GitHub
Browse files

Replace `util::either` with `util::expected`. (#1142)

* Implement a workalike for the proposed `std::expected` class: see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0323r9.html .
* Replace use of `either` with `expected` in `mprovider`, `mechanism_catalogue`, `util::partition_range`, and `pyarb::hopefully`.
* Replace use of `either` with `variant` in `util::sentinel_iterator`.
* Add `in_place_t` constructor for `util::optional`.
* Fix move assignment bug in `util::variant`.
* Remove `util/either.hpp` and associated tests.

Fixes #1135. 
parent 74411404
No related branches found
No related tags found
No related merge requests found
Showing with 972 additions and 556 deletions
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#include <arbor/morph/embed_pwlin.hpp> #include <arbor/morph/embed_pwlin.hpp>
#include <arbor/morph/primitives.hpp> #include <arbor/morph/primitives.hpp>
#include <arbor/morph/label_dict.hpp> #include <arbor/morph/label_dict.hpp>
#include <arbor/util/either.hpp> #include <arbor/util/expected.hpp>
namespace arb { namespace arb {
...@@ -34,8 +34,8 @@ private: ...@@ -34,8 +34,8 @@ private:
struct circular_def {}; struct circular_def {};
// Maps are mutated only during initialization phase of mprovider. // Maps are mutated only during initialization phase of mprovider.
mutable std::unordered_map<std::string, util::either<mextent, circular_def>> regions_; mutable std::unordered_map<std::string, util::expected<mextent, circular_def>> regions_;
mutable std::unordered_map<std::string, util::either<mlocation_list, circular_def>> locsets_; mutable std::unordered_map<std::string, util::expected<mlocation_list, circular_def>> locsets_;
// Non-null only during initialization phase. // Non-null only during initialization phase.
mutable const label_dict* label_dict_ptr; mutable const label_dict* label_dict_ptr;
......
#pragma once
/*
* A type-safe discriminated union of two members.
*
* Returns true in a bool context if the first of the two types holds a value.
*/
#include <cstdlib>
#include <type_traits>
#include <stdexcept>
#include <utility>
#include <arbor/util/uninitialized.hpp>
namespace arb {
namespace util {
struct either_invalid_access: std::runtime_error {
explicit either_invalid_access(const std::string& what_str)
: std::runtime_error(what_str)
{}
either_invalid_access()
: std::runtime_error("access of unconstructed value in either")
{}
};
namespace detail {
template <typename A, typename B>
struct either_data {
union {
uninitialized<A> ua;
uninitialized<B> ub;
};
either_data() = default;
either_data(const either_data&) = delete;
either_data(either_data&&) = delete;
either_data& operator=(const either_data&) = delete;
either_data& operator=(either_data&&) = delete;
};
template <std::size_t, typename A, typename B> struct either_select;
template <typename A, typename B>
struct either_select<0, A, B> {
using type = uninitialized<A>;
static type& field(either_data<A, B>& data) { return data.ua; }
static const type& field(const either_data<A, B>& data) { return data.ua; }
};
template <typename A, typename B>
struct either_select<1, A, B> {
using type = uninitialized<B>;
static type& field(either_data<A, B>& data) { return data.ub; }
static const type& field(const either_data<A, B>& data) { return data.ub; }
};
template <std::size_t I, typename A, typename B>
struct either_get: either_select<I, A, B> {
using typename either_select<I, A, B>::type;
using either_select<I, A, B>::field;
static typename type::reference unsafe_get(either_data<A, B>& data) {
return field(data).ref();
}
static typename type::const_reference unsafe_get(const either_data<A, B>& data) {
return field(data).cref();
}
static typename type::reference unsafe_get(char which, either_data<A, B>& data) {
if (I!=which) {
throw either_invalid_access();
}
return field(data).ref();
}
static typename type::const_reference unsafe_get(char which, const either_data<A, B>& data) {
if (I!=which) {
throw either_invalid_access();
}
return field(data).cref();
}
static typename type::pointer ptr(char which, either_data<A, B>& data) {
return I==which? field(data).ptr(): nullptr;
}
static typename type::const_pointer ptr(char which, const either_data<A, B>& data) {
return I==which? field(data).cptr(): nullptr;
}
};
} // namespace detail
constexpr std::size_t variant_npos = static_cast<std::size_t>(-1); // emulating C++17 variant type
template <typename A, typename B>
class either: public detail::either_data<A, B> {
using base = detail::either_data<A, B>;
using base::ua;
using base::ub;
template <std::size_t I>
using getter = detail::either_get<I, A, B>;
unsigned char which;
public:
// default ctor if A is default-constructible or A is not and B is.
template <
typename A_ = A,
bool a_ = std::is_default_constructible<A_>::value,
bool b_ = std::is_default_constructible<B>::value,
typename = std::enable_if_t<a_ || (!a_ && b_)>,
std::size_t w_ = a_? 0: 1
>
either() noexcept(std::is_nothrow_default_constructible<typename getter<w_>::type>::value):
which(w_)
{
getter<w_>::field(*this).construct();
}
// implicit constructors from A and B values by copy or move
either(const A& a) noexcept(std::is_nothrow_copy_constructible<A>::value): which(0) {
getter<0>::field(*this).construct(a);
}
template <
typename B_ = B,
typename = std::enable_if_t<!std::is_same<A, B_>::value>
>
either(const B& b) noexcept(std::is_nothrow_copy_constructible<B>::value): which(1) {
getter<1>::field(*this).construct(b);
}
either(A&& a) noexcept(std::is_nothrow_move_constructible<A>::value): which(0) {
getter<0>::field(*this).construct(std::move(a));
}
template <
typename B_ = B,
typename = std::enable_if_t<!std::is_same<A, B_>::value>
>
either(B&& b) noexcept(std::is_nothrow_move_constructible<B>::value): which(1) {
getter<1>::field(*this).construct(std::move(b));
}
// copy constructor
either(const either& x)
noexcept(std::is_nothrow_copy_constructible<A>::value &&
std::is_nothrow_copy_constructible<B>::value):
which(x.which)
{
if (which==0) {
getter<0>::field(*this).construct(x.unsafe_get<0>());
}
else if (which==1) {
getter<1>::field(*this).construct(x.unsafe_get<1>());
}
}
// move constructor
either(either&& x)
noexcept(std::is_nothrow_move_constructible<A>::value &&
std::is_nothrow_move_constructible<B>::value):
which(x.which)
{
if (which==0) {
getter<0>::field(*this).construct(std::move(x.unsafe_get<0>()));
}
else if (which==1) {
getter<1>::field(*this).construct(std::move(x.unsafe_get<1>()));
}
}
// copy assignment
either& operator=(const either& x) {
if (this==&x) {
return *this;
}
switch (which) {
case 0:
if (x.which==0) {
unsafe_get<0>() = x.unsafe_get<0>();
}
else {
if (x.which==1) {
B b_tmp(x.unsafe_get<1>());
getter<0>::field(*this).destruct();
which = (unsigned char)variant_npos;
getter<1>::field(*this).construct(std::move(b_tmp));
which = 1;
}
else {
getter<0>::field(*this).destruct();
which = (unsigned char)variant_npos;
}
}
break;
case 1:
if (x.which==1) {
unsafe_get<1>() = x.unsafe_get<1>();
}
else {
if (x.which==0) {
A a_tmp(x.unsafe_get<0>());
getter<1>::field(*this).destruct();
which = (unsigned char)variant_npos;
getter<0>::field(*this).construct(std::move(a_tmp));
which = 0;
}
else {
getter<1>::field(*this).destruct();
which = (unsigned char)variant_npos;
}
}
break;
default: // variant_npos
if (x.which==0) {
getter<0>::field(*this).construct(x.unsafe_get<0>());
}
else if (x.which==1) {
getter<1>::field(*this).construct(x.unsafe_get<1>());
}
break;
}
return *this;
}
// move assignment
either& operator=(either&& x) {
if (this==&x) {
return *this;
}
switch (which) {
case 0:
if (x.which==0) {
unsafe_get<0>() = std::move(x.unsafe_get<0>());
}
else {
which = (unsigned char)variant_npos;
getter<0>::field(*this).destruct();
if (x.which==1) {
getter<1>::field(*this).construct(std::move(x.unsafe_get<1>()));
which = 1;
}
}
break;
case 1:
if (x.which==1) {
unsafe_get<1>() = std::move(x.unsafe_get<1>());
}
else {
which = (unsigned char)variant_npos;
getter<1>::field(*this).destruct();
if (x.which==0) {
getter<0>::field(*this).construct(std::move(x.unsafe_get<0>()));
which = 0;
}
}
break;
default: // variant_npos
if (x.which==0) {
getter<0>::field(*this).construct(std::move(x.unsafe_get<0>()));
}
else if (x.which==1) {
getter<1>::field(*this).construct(std::move(x.unsafe_get<1>()));
}
break;
}
return *this;
}
// unchecked element access
template <std::size_t I>
typename getter<I>::type::reference unsafe_get() {
return getter<I>::unsafe_get(*this);
}
template <std::size_t I>
typename getter<I>::type::const_reference unsafe_get() const {
return getter<I>::unsafe_get(*this);
}
// checked element access
template <std::size_t I>
typename getter<I>::type::reference get() {
return getter<I>::unsafe_get(which, *this);
}
template <std::size_t I>
typename getter<I>::type::const_reference get() const {
return getter<I>::unsafe_get(which, *this);
}
// convenience getter aliases
typename getter<0>::type::reference first() { return get<0>(); }
typename getter<0>::type::const_reference first() const { return get<0>(); }
typename getter<1>::type::reference second() { return get<1>(); }
typename getter<1>::type::const_reference second() const { return get<1>(); }
// pointer to element access: return nullptr if it does not hold this item
template <std::size_t I>
auto ptr() {
return getter<I>::ptr(which, *this);
}
template <std::size_t I>
auto ptr() const {
return getter<I>::ptr(which, *this);
}
// true in bool context if holds first alternative
constexpr operator bool() const { return which==0; }
constexpr bool valueless_by_exception() const noexcept {
return which==(unsigned char)variant_npos;
}
constexpr std::size_t index() const noexcept {
return which;
}
~either() {
if (which==0) {
getter<0>::field(*this).destruct();
}
else if (which==1) {
getter<1>::field(*this).destruct();
}
}
// comparison operators follow C++17 variant semantics
bool operator==(const either& x) const {
return index()==x.index() &&
index()==0? unsafe_get<0>()==x.unsafe_get<0>():
index()==1? unsafe_get<1>()==x.unsafe_get<1>():
true;
}
bool operator!=(const either& x) const {
return index()!=x.index() ||
index()==0? unsafe_get<0>()!=x.unsafe_get<0>():
index()==1? unsafe_get<1>()!=x.unsafe_get<1>():
false;
}
bool operator<(const either& x) const {
return !x.valueless_by_exception() &&
index()==0? (x.index()==1 || unsafe_get<0>()<x.unsafe_get<0>()):
index()==1? (x.index()!=0 && unsafe_get<1>()<x.unsafe_get<1>()):
true;
}
bool operator>=(const either& x) const {
return x.valueless_by_exception() ||
index()==0? (x.index()!=1 && unsafe_get<0>()>=x.unsafe_get<0>()):
index()==1? (x.index()==0 || unsafe_get<1>()>=x.unsafe_get<1>()):
false;
}
bool operator<=(const either& x) const {
return valueless_by_exception() ||
x.index()==0? (index()!=1 && unsafe_get<0>()<=x.unsafe_get<0>()):
x.index()==1? (index()==0 || unsafe_get<1>()<=x.unsafe_get<1>()):
false;
}
bool operator>(const either& x) const {
return !valueless_by_exception() &&
x.index()==0? (index()==1 || unsafe_get<0>()>x.unsafe_get<0>()):
x.index()==1? (index()!=0 && unsafe_get<1>()>x.unsafe_get<1>()):
true;
}
};
} // namespace util
} // namespace arb
#pragma once
// C++14 version of the proposed std::expected class
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0323r9.html
//
// Difference from proposal:
//
// * Left out lots of constexpr.
// * Lazy about explicitness of some conversions.
#include <initializer_list>
#include <optional>
#include <type_traits>
#include <utility>
#include <variant>
namespace arb {
namespace util {
namespace detail {
// True if T can constructed from or converted from [const, ref] X.
template <typename T, typename X>
struct conversion_hazard: std::integral_constant<bool,
std::is_constructible_v<T, X> ||
std::is_constructible_v<T, const X> ||
std::is_constructible_v<T, X&> ||
std::is_constructible_v<T, const X&> ||
std::is_convertible_v<X, T> ||
std::is_convertible_v<const X, T> ||
std::is_convertible_v<X&, T> ||
std::is_convertible_v<const X&, T>> {};
template <typename T, typename X>
inline constexpr bool conversion_hazard_v = conversion_hazard<T, X>::value;
// TODO: C++20 replace with std::remove_cvref_t
template <typename X>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<X>>;
} // namespace detail
struct unexpect_t {};
inline constexpr unexpect_t unexpect{};
template <typename E = void>
struct bad_expected_access;
template <>
struct bad_expected_access<void>: std::exception {
bad_expected_access() {}
virtual const char* what() const noexcept override { return "bad expected access"; }
};
template <typename E>
struct bad_expected_access: public bad_expected_access<void> {
explicit bad_expected_access(E error): error_(error) {}
E& error() & { return error_; }
const E& error() const& { return error_; }
E&& error() && { return std::move(error_); }
const E&& error() const && { return std::move(error_); }
private:
E error_;
};
// The unexpected<E> wrapper is mainly boiler-plate for a box that wraps
// a value of type E, with corresponding conversion and assignment semantics.
template <typename E>
struct unexpected {
template <typename F>
friend class unexpected;
unexpected() = default;
unexpected(const unexpected&) = default;
unexpected(unexpected&&) = default;
// Emplace-style ctors.
template <typename... Args>
explicit unexpected(std::in_place_t, Args&&... args):
value_(std::forward<Args>(args)...) {}
template <typename X, typename... Args>
explicit unexpected(std::in_place_t, std::initializer_list<X> il, Args&&... args):
value_(il, std::forward<Args>(args)...) {}
// Converting ctors.
template <typename F,
typename = std::enable_if_t<std::is_constructible_v<E, F&&>>,
typename = std::enable_if_t<!std::is_same_v<std::in_place_t, detail::remove_cvref_t<F>>>,
typename = std::enable_if_t<!std::is_same_v<unexpected, detail::remove_cvref_t<F>>>
>
explicit unexpected(F&& f): value_(std::forward<F>(f)) {}
template <
typename F,
typename = std::enable_if_t<!detail::conversion_hazard_v<E, unexpected<F>>>
>
unexpected(unexpected<F>&& u): value_(std::move(u.value_)) {}
template <
typename F,
typename = std::enable_if_t<!detail::conversion_hazard_v<E, unexpected<F>>>
>
unexpected(const unexpected<F>& u): value_(u.value_) {}
// Assignment operators.
unexpected& operator=(const unexpected& u) { return value_ = u.value_, *this; }
unexpected& operator=(unexpected&& u) { return value_ = std::move(u.value_), *this; }
template <typename F>
unexpected& operator=(const unexpected<F>& u) { return value_ = u.value_, *this; }
template <typename F>
unexpected& operator=(unexpected<F>&& u) { return value_ = std::move(u.value_), *this; }
// Access.
E& value() & { return value_; }
const E& value() const & { return value_; }
E&& value() && { return std::move(value_); }
const E&& value() const && { return std::move(value_); }
// Equality.
template <typename F>
bool operator==(const unexpected<F>& other) const { return value()==other.value(); }
template <typename F>
bool operator!=(const unexpected<F>& other) const { return value()!=other.value(); }
// Swap.
void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v<E>) {
using std::swap;
swap(value_, other.value_);
}
friend void swap(unexpected& a, unexpected& b) noexcept(noexcept(a.swap(b))) { a.swap(b); }
private:
E value_;
};
template <typename E>
unexpected(E) -> unexpected<E>;
template <typename T, typename E>
struct expected {
using value_type = T;
using error_type = E;
using unexpected_type = unexpected<E>;
using data_type = std::variant<T, unexpected_type>;
expected() = default;
expected(const expected&) = default;
expected(expected&&) = default;
// Emplace-style ctors.
template <typename... Args>
explicit expected(std::in_place_t, Args&&... args):
data_(std::in_place_index<0>, std::forward<Args>(args)...) {}
template <typename X, typename... Args>
explicit expected(std::in_place_t, std::initializer_list<X> il, Args&&... args):
data_(std::in_place_index<0>, il, std::forward<Args>(args)...) {}
// (Proposal says to forward args to unexpected<E>, but this is not compatible
// with the requirement that E is constructible from args; so here we're forwarding
// to unexpected<E> with an additional 'in_place' argument.)
template <typename... Args>
explicit expected(unexpect_t, Args&&... args):
data_(std::in_place_index<1>, std::in_place_t{}, std::forward<Args>(args)...) {}
template <typename X, typename... Args>
explicit expected(unexpect_t, std::initializer_list<X> il, Args&&... args):
data_(std::in_place_index<1>, std::in_place_t{}, il, std::forward<Args>(args)...) {}
// Converting ctors.
template <
typename S,
typename F,
typename = std::enable_if_t<!detail::conversion_hazard_v<T, expected<S, F>>>,
typename = std::enable_if_t<!detail::conversion_hazard_v<unexpected<E>, expected<S, F>>>
>
expected(const expected<S, F>& other):
data_(other? data_type(std::in_place_index<0>, *other): data_type(std::in_place_index<1>, other.error()))
{}
template <
typename S,
typename F,
typename = std::enable_if_t<!detail::conversion_hazard_v<T, expected<S, F>>>,
typename = std::enable_if_t<!detail::conversion_hazard_v<unexpected<E>, expected<S, F>>>
>
expected(expected<S, F>&& other):
data_(other? data_type(std::in_place_index<0>, *std::move(other)): data_type(std::in_place_index<1>, std::move(other).error()))
{}
template <
typename S,
typename = std::enable_if_t<std::is_constructible_v<T, S&&>>,
typename = std::enable_if_t<!std::is_same_v<std::in_place_t, detail::remove_cvref_t<S>>>,
typename = std::enable_if_t<!std::is_same_v<expected, detail::remove_cvref_t<S>>>,
typename = std::enable_if_t<!std::is_same_v<unexpected<E>, detail::remove_cvref_t<S>>>
>
expected(S&& x): data_(std::in_place_index<0>, std::forward<S>(x)) {}
template <typename F>
expected(const unexpected<F>& u): data_(std::in_place_index<1>, u) {}
template <typename F>
expected(unexpected<F>&& u): data_(std::in_place_index<1>, std::move(u)) {}
// Assignment ops.
expected& operator=(const expected& other) noexcept(std::is_nothrow_copy_assignable_v<data_type>) {
data_ = other.data_;
return *this;
}
expected& operator=(expected&& other) noexcept(std::is_nothrow_move_assignable_v<data_type>) {
data_ = std::move(other.data_);
return *this;
}
template <
typename S,
typename = std::enable_if_t<!std::is_same_v<expected, detail::remove_cvref_t<S>>>,
typename = std::enable_if_t<std::is_constructible_v<T, S>>,
typename = std::enable_if_t<std::is_assignable_v<T&, S>>
>
expected& operator=(S&& v) {
data_ = data_type(std::in_place_index<0>, std::forward<S>(v));
return *this;
}
template <typename F>
expected& operator=(const unexpected<F>& u) {
data_ = data_type(std::in_place_index<1>, u);
return *this;
}
template <typename F>
expected& operator=(unexpected<F>&& u) {
data_ = data_type(std::in_place_index<1>, std::move(u));
return *this;
}
// Emplace ops.
template <typename... Args>
T& emplace(Args&&... args) {
data_ = data_type(std::in_place_index<0>, std::forward<Args>(args)...);
return value();
}
template <typename U, typename... Args>
T& emplace(std::initializer_list<U> il, Args&&... args) {
data_ = data_type(std::in_place_index<0>, il, std::forward<Args>(args)...);
return value();
}
// Swap ops.
void swap(expected& other) noexcept(std::is_nothrow_swappable_v<data_type>) {
data_.swap(other.data_);
}
friend void swap(expected& a, expected& b) { a.swap(b); }
// Accessors.
bool has_value() const noexcept { return data_.index()==0; }
explicit operator bool() const noexcept { return has_value(); }
T& value() & {
if (*this) return std::get<0>(data_);
throw bad_expected_access(error());
}
const T& value() const& {
if (*this) return std::get<0>(data_);
throw bad_expected_access(error());
}
T&& value() && {
if (*this) return std::get<0>(std::move(data_));
throw bad_expected_access(error());
}
const T&& value() const&& {
if (*this) return std::get<0>(std::move(data_));
throw bad_expected_access(error());
}
const E& error() const& { return std::get<1>(data_).value(); }
E& error() & { return std::get<1>(data_).value(); }
const E&& error() const&& { return std::get<1>(std::move(data_)).value(); }
E&& error() && { return std::get<1>(std::move(data_)).value(); }
const T& operator*() const& { return std::get<0>(data_); }
T& operator*() & { return std::get<0>(data_); }
const T&& operator*() const&& { return std::get<0>(std::move(data_)); }
T&& operator*() && { return std::get<0>(std::move(data_)); }
const T* operator->() const { return std::get_if<0>(&data_); }
T* operator->() { return std::get_if<0>(&data_); }
template <typename S>
T value_or(S&& s) const& { return has_value()? value(): std::forward<S>(s); }
template <typename S>
T value_or(S&& s) && { return has_value()? value(): std::forward<S>(s); }
private:
data_type data_;
};
// Equality comparisons for non-void expected.
template <typename T1, typename E1, typename T2, typename E2,
typename = std::enable_if_t<!std::is_void_v<T1>>,
typename = std::enable_if_t<!std::is_void_v<T2>>>
inline bool operator==(const expected<T1, E1>& a, const expected<T2, E2>& b) {
return a? b && a.value()==b.value(): !b && a.error()==b.error();
}
template <typename T1, typename E1, typename T2, typename E2,
typename = std::enable_if_t<!std::is_void_v<T1>>,
typename = std::enable_if_t<!std::is_void_v<T2>>>
inline bool operator!=(const expected<T1, E1>& a, const expected<T2, E2>& b) {
return a? !b || a.value()!=b.value(): b || a.error()!=b.error();
}
template <typename T1, typename E1, typename T2,
typename = std::enable_if_t<!std::is_void_v<T1>>,
typename = decltype(static_cast<bool>(std::declval<const expected<T1, E1>>().value()==std::declval<T2>()))>
inline bool operator==(const expected<T1, E1>& a, const T2& v) {
return a && a.value()==v;
}
template <typename T1, typename E1, typename T2,
typename = std::enable_if_t<!std::is_void_v<T1>>,
typename = decltype(static_cast<bool>(std::declval<const expected<T1, E1>>().value()==std::declval<T2>()))>
inline bool operator==(const T2& v, const expected<T1, E1>& a) {
return a==v;
}
template <typename T1, typename E1, typename T2,
typename = std::enable_if_t<!std::is_void_v<T1>>,
typename = decltype(static_cast<bool>(std::declval<const expected<T1, E1>>().value()!=std::declval<T2>()))>
inline bool operator!=(const expected<T1, E1>& a, const T2& v) {
return !a || a.value()!=v;
}
template <typename T1, typename E1, typename T2,
typename = std::enable_if_t<!std::is_void_v<T1>>,
typename = decltype(static_cast<bool>(std::declval<const expected<T1, E1>>().value()!=std::declval<T2>()))>
inline bool operator!=(const T2& v, const expected<T1, E1>& a) {
return a!=v;
}
// Equality comparisons against unexpected.
template <typename T1, typename E1, typename E2,
typename = decltype(static_cast<bool>(unexpected(std::declval<const expected<T1, E1>>().error())
==std::declval<const unexpected<E2>>()))>
inline bool operator==(const expected<T1, E1>& a, const unexpected<E2>& u) {
return !a && unexpected(a.error())==u;
}
template <typename T1, typename E1, typename E2,
typename = decltype(static_cast<bool>(unexpected(std::declval<const expected<T1, E1>>().error())
==std::declval<const unexpected<E2>>()))>
inline bool operator==(const unexpected<E2>& u, const expected<T1, E1>& a) {
return a==u;
}
template <typename T1, typename E1, typename E2,
typename = decltype(static_cast<bool>(unexpected(std::declval<const expected<T1, E1>>().error())
!=std::declval<const unexpected<E2>>()))>
inline bool operator!=(const expected<T1, E1>& a, const unexpected<E2>& u) {
return a || unexpected(a.error())!=u;
}
template <typename T1, typename E1, typename E2,
typename = decltype(static_cast<bool>(unexpected(std::declval<const expected<T1, E1>>().error())
!=std::declval<const unexpected<E2>>()))>
inline bool operator!=(const unexpected<E2>& u, const expected<T1, E1>& a) {
return a!=u;
}
template <typename E>
struct expected<void, E> {
using value_type = void;
using error_type = E;
using unexpected_type = unexpected<E>;
using data_type = std::optional<unexpected_type>;
expected() = default;
expected(const expected&) = default;
expected(expected&&) = default;
// Emplace-style ctors.
explicit expected(std::in_place_t) {}
template <typename... Args>
explicit expected(unexpect_t, Args&&... args):
data_(std::in_place_t{}, std::in_place_t{}, std::forward<Args>(args)...) {}
template <typename X, typename... Args>
explicit expected(unexpect_t, std::initializer_list<X> il, Args&&... args):
data_(std::in_place_t{}, std::in_place_t{}, il, std::forward<Args>(args)...) {}
// Converting ctors.
template <
typename F,
typename = std::enable_if_t<!detail::conversion_hazard_v<unexpected<E>, expected<void, F>>>
>
expected(const expected<void, F>& other): data_(other.data_) {}
template <
typename F,
typename = std::enable_if_t<!detail::conversion_hazard_v<unexpected<E>, expected<void, F>>>
>
expected(expected<void, F>&& other): data_(std::move(other.data_)) {}
template <typename F>
expected(const unexpected<F>& u): data_(u) {}
template <typename F>
expected(unexpected<F>&& u): data_(std::move(u)) {}
// Assignment ops.
expected& operator=(const expected& other) noexcept(std::is_nothrow_copy_assignable_v<data_type>) {
data_ = other.data_;
return *this;
}
expected& operator=(expected&& other) noexcept(std::is_nothrow_move_assignable_v<data_type>) {
data_ = std::move(other.data_);
return *this;
}
template <typename F>
expected& operator=(const unexpected<F>& u) {
data_ = u;
return *this;
}
template <typename F>
expected& operator=(unexpected<F>&& u) {
data_ = std::move(u);
return *this;
}
// No emplace ops.
// Swap ops.
void swap(expected& other) {
// TODO: C++17 just use std::optional::swap; haven't implemented util::optional::swap.
if (data_) {
if (other.data_) {
std::swap(*data_, *other.data_);
}
else {
other.data_ = std::move(data_);
data_.reset();
}
}
else if (other.data_) {
data_ = std::move(other.data_);
other.data_.reset();
}
}
// Accessors.
bool has_value() const noexcept { return !data_; }
explicit operator bool() const noexcept { return has_value(); }
void value() const {
if (!has_value()) throw bad_expected_access<E>(error());
}
const E& error() const& { return data_->value(); }
E& error() & { return data_->value(); }
const E&& error() const&& { return std::move(data_->value()); }
E&& error() && { return std::move(data_->value()); }
private:
data_type data_;
};
// Equality comparisons for void expected.
template <typename T1, typename E1, typename T2, typename E2,
typename = std::enable_if_t<std::is_void_v<T1> || std::is_void_v<T2>>>
inline bool operator==(const expected<T1, E1>& a, const expected<T2, E2>& b) {
return a? b && std::is_void_v<T1> && std::is_void_v<T2>: !b && a.error()==b.error();
}
template <typename T1, typename E1, typename T2, typename E2,
typename = std::enable_if_t<std::is_void_v<T1> || std::is_void_v<T2>>>
inline bool operator!=(const expected<T1, E1>& a, const expected<T2, E2>& b) {
return a? !b || !std::is_void_v<T2> || !std::is_void_v<T1>: b || a.error()!=b.error();
}
} // namespace util
} // namespace arb
...@@ -14,8 +14,6 @@ ...@@ -14,8 +14,6 @@
* *
* 4. `swap()` method and ADL-available `swap()` function. * 4. `swap()` method and ADL-available `swap()` function.
* *
* 5. In-place construction with `std::in_place_t` tags or equivalent.
*
* 5. No `make_optional` function (but see `just` below). * 5. No `make_optional` function (but see `just` below).
* *
* Additional/differing functionality: * Additional/differing functionality:
...@@ -126,6 +124,13 @@ namespace detail { ...@@ -126,6 +124,13 @@ namespace detail {
} }
} }
template <typename... Args>
optional_base(bool set_, std::in_place_t, Args&&... args) : set(set_) {
if (set) {
data.construct(std::forward<Args>(args)...);
}
}
reference ref() { return data.ref(); } reference ref() { return data.ref(); }
const_reference ref() const { return data.cref(); } const_reference ref() const { return data.cref(); }
...@@ -208,6 +213,12 @@ struct optional: detail::optional_base<X> { ...@@ -208,6 +213,12 @@ struct optional: detail::optional_base<X> {
optional(X&& x) optional(X&& x)
noexcept(std::is_nothrow_move_constructible<X>::value): base(true, std::move(x)) {} noexcept(std::is_nothrow_move_constructible<X>::value): base(true, std::move(x)) {}
template <typename... Args>
optional(std::in_place_t, Args&&... args): base(true, std::in_place_t{}, std::forward<Args>(args)...) {}
template <typename U, typename... Args>
optional(std::in_place_t, std::initializer_list<U> il, Args&&... args): base(true, std::in_place_t{}, il, std::forward<Args>(args)...) {}
optional(const optional& ot): base(ot.set, ot.ref()) {} optional(const optional& ot): base(ot.set, ot.ref()) {}
template <typename T> template <typename T>
......
...@@ -172,7 +172,7 @@ struct variant_dynamic_impl<> { ...@@ -172,7 +172,7 @@ struct variant_dynamic_impl<> {
if (i!=std::size_t(-1)) throw bad_variant_access{}; if (i!=std::size_t(-1)) throw bad_variant_access{};
} }
static void move_assign(std::size_t i, char* data, const char* from) { static void move_assign(std::size_t i, char* data, char* from) {
if (i!=std::size_t(-1)) throw bad_variant_access{}; if (i!=std::size_t(-1)) throw bad_variant_access{};
} }
...@@ -220,9 +220,9 @@ struct variant_dynamic_impl<H, T...> { ...@@ -220,9 +220,9 @@ struct variant_dynamic_impl<H, T...> {
} }
} }
static void move_assign(std::size_t i, char* data, const char* from) { static void move_assign(std::size_t i, char* data, char* from) {
if (i==0) { if (i==0) {
*reinterpret_cast<H*>(data) = std::move(*reinterpret_cast<const H*>(from)); *reinterpret_cast<H*>(data) = std::move(*reinterpret_cast<H*>(from));
} }
else { else {
variant_dynamic_impl<T...>::move_assign(i-1, data, from); variant_dynamic_impl<T...>::move_assign(i-1, data, from);
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#include <arbor/arbexcept.hpp> #include <arbor/arbexcept.hpp>
#include <arbor/mechcat.hpp> #include <arbor/mechcat.hpp>
#include <arbor/util/either.hpp> #include <arbor/util/expected.hpp>
#include "util/maputil.hpp" #include "util/maputil.hpp"
...@@ -53,15 +53,14 @@ ...@@ -53,15 +53,14 @@
* mechanism. * mechanism.
* *
* The private implementation class catalogue_state does not throw any (catalogue * The private implementation class catalogue_state does not throw any (catalogue
* related) exceptions, but instead propagates errors via util::either to the * related) exceptions, but instead propagates errors via util::expected to the
* mechanism_catalogue methods for handling. * mechanism_catalogue methods for handling.
*/ */
namespace arb { namespace arb {
using util::value_by_key; using util::value_by_key;
using util::optional; using util::unexpected;
using util::nullopt;
using std::make_unique; using std::make_unique;
using std::make_exception_ptr; using std::make_exception_ptr;
...@@ -72,42 +71,26 @@ template <typename V> ...@@ -72,42 +71,26 @@ template <typename V>
using string_map = std::unordered_map<std::string, V>; using string_map = std::unordered_map<std::string, V>;
template <typename T> template <typename T>
struct hopefully_typemap { using hopefully = util::expected<T, std::exception_ptr>;
using type = util::either<T, std::exception_ptr>;
};
template <>
struct hopefully_typemap<void> {
struct placeholder_type {};
using type = util::either<placeholder_type, std::exception_ptr>;
};
template <typename T>
using hopefully = typename hopefully_typemap<T>::type;
namespace {
// Convert hopefully<T> to T or throw. // Convert hopefully<T> to T or throw.
template <typename T> template <typename T>
const T& value(const util::either<T, std::exception_ptr>& x) { const T& value(const hopefully<T>& x) {
if (!x) { if (!x) std::rethrow_exception(x.error());
std::rethrow_exception(x.second()); return x.value();
}
return x.first();
} }
template <typename T> template <typename T>
T value(util::either<T, std::exception_ptr>&& x) { T value(hopefully<T>&& x) {
if (!x) { if (!x) std::rethrow_exception(std::move(x).error());
std::rethrow_exception(x.second()); return std::move(x).value();
}
return std::move(x.first());
} }
void value(const hopefully<void>& x) { // Conveniently make an unexpected exception_ptr:
if (!x) { template <typename X>
std::rethrow_exception(x.second()); auto unexpected_exception_ptr(X x) { return unexpected(make_exception_ptr(std::move(x))); }
} } // anonymous namespace
}
struct derivation { struct derivation {
std::string parent; std::string parent;
...@@ -187,17 +170,16 @@ struct catalogue_state { ...@@ -187,17 +170,16 @@ struct catalogue_state {
// Register concrete mechanism for a back-end type. // Register concrete mechanism for a back-end type.
hopefully<void> register_impl(std::type_index tidx, const std::string& name, std::unique_ptr<mechanism> mech) { hopefully<void> register_impl(std::type_index tidx, const std::string& name, std::unique_ptr<mechanism> mech) {
if (auto fptr = fingerprint_ptr(name)) { if (auto fptr = fingerprint_ptr(name)) {
if (mech->fingerprint()!=*fptr.first()) { if (mech->fingerprint()!=*fptr.value()) {
return make_exception_ptr(fingerprint_mismatch(name)); return unexpected_exception_ptr(fingerprint_mismatch(name));
} }
impl_map_[name][tidx] = std::move(mech); impl_map_[name][tidx] = std::move(mech);
return {};
} }
else { else {
return fptr.second(); return unexpected(fptr.error());
} }
return {};
} }
// Remove mechanism and its derivations and implementations. // Remove mechanism and its derivations and implementations.
...@@ -234,10 +216,10 @@ struct catalogue_state { ...@@ -234,10 +216,10 @@ struct catalogue_state {
return *(p->get()); return *(p->get());
} }
else if (auto deriv = derive(name)) { else if (auto deriv = derive(name)) {
return *(deriv.first().derived_info.get()); return *(deriv->derived_info.get());
} }
else { else {
return deriv.second(); return unexpected(deriv.error());
} }
} }
...@@ -249,10 +231,10 @@ struct catalogue_state { ...@@ -249,10 +231,10 @@ struct catalogue_state {
if (!defined(name)) { if (!defined(name)) {
if ((implicit_deriv = derive(name))) { if ((implicit_deriv = derive(name))) {
base = &implicit_deriv.first().parent; base = &implicit_deriv->parent;
} }
else { else {
return implicit_deriv.second(); return unexpected(implicit_deriv.error());
} }
} }
...@@ -274,10 +256,10 @@ struct catalogue_state { ...@@ -274,10 +256,10 @@ struct catalogue_state {
const std::vector<std::pair<std::string, std::string>>& ion_remap_vec) const const std::vector<std::pair<std::string, std::string>>& ion_remap_vec) const
{ {
if (defined(name)) { if (defined(name)) {
return make_exception_ptr(duplicate_mechanism(name)); return unexpected_exception_ptr(duplicate_mechanism(name));
} }
else if (!defined(parent)) { else if (!defined(parent)) {
return make_exception_ptr(no_such_mechanism(parent)); return unexpected_exception_ptr(no_such_mechanism(parent));
} }
string_map<std::string> ion_remap_map(ion_remap_vec.begin(), ion_remap_vec.end()); string_map<std::string> ion_remap_map(ion_remap_vec.begin(), ion_remap_vec.end());
...@@ -285,10 +267,10 @@ struct catalogue_state { ...@@ -285,10 +267,10 @@ struct catalogue_state {
mechanism_info_ptr new_info; mechanism_info_ptr new_info;
if (auto parent_info = info(parent)) { if (auto parent_info = info(parent)) {
new_info.reset(new mechanism_info(parent_info.first())); new_info.reset(new mechanism_info(parent_info.value()));
} }
else { else {
return parent_info.second(); return unexpected(parent_info.error());
} }
// Update global parameter values in info for derived mechanism. // Update global parameter values in info for derived mechanism.
...@@ -299,11 +281,11 @@ struct catalogue_state { ...@@ -299,11 +281,11 @@ struct catalogue_state {
if (auto p = value_by_key(new_info->globals, param)) { if (auto p = value_by_key(new_info->globals, param)) {
if (!p->valid(value)) { if (!p->valid(value)) {
return make_exception_ptr(invalid_parameter_value(name, param, value)); return unexpected_exception_ptr(invalid_parameter_value(name, param, value));
} }
} }
else { else {
return make_exception_ptr(no_such_parameter(name, param)); return unexpected_exception_ptr(no_such_parameter(name, param));
} }
deriv.globals[param] = value; deriv.globals[param] = value;
...@@ -312,7 +294,7 @@ struct catalogue_state { ...@@ -312,7 +294,7 @@ struct catalogue_state {
for (const auto& kv: ion_remap_vec) { for (const auto& kv: ion_remap_vec) {
if (!new_info->ions.count(kv.first)) { if (!new_info->ions.count(kv.first)) {
return make_exception_ptr(invalid_ion_remap(name, kv.first, kv.second)); return unexpected_exception_ptr(invalid_ion_remap(name, kv.first, kv.second));
} }
} }
...@@ -322,7 +304,7 @@ struct catalogue_state { ...@@ -322,7 +304,7 @@ struct catalogue_state {
for (const auto& kv: new_info->ions) { for (const auto& kv: new_info->ions) {
if (auto new_ion = value_by_key(ion_remap_map, kv.first)) { if (auto new_ion = value_by_key(ion_remap_map, kv.first)) {
if (!new_ions.insert({*new_ion, kv.second}).second) { if (!new_ions.insert({*new_ion, kv.second}).second) {
return make_exception_ptr(invalid_ion_remap(name, kv.first, *new_ion)); return unexpected_exception_ptr(invalid_ion_remap(name, kv.first, *new_ion));
} }
} }
else { else {
...@@ -330,7 +312,7 @@ struct catalogue_state { ...@@ -330,7 +312,7 @@ struct catalogue_state {
// (find offending remap to report in exception) // (find offending remap to report in exception)
for (const auto& entry: ion_remap_map) { for (const auto& entry: ion_remap_map) {
if (entry.second==kv.first) { if (entry.second==kv.first) {
return make_exception_ptr(invalid_ion_remap(name, kv.first, entry.second)); return unexpected_exception_ptr(invalid_ion_remap(name, kv.first, entry.second));
} }
} }
throw arbor_internal_error("inconsistent catalogue ion remap state"); throw arbor_internal_error("inconsistent catalogue ion remap state");
...@@ -340,23 +322,23 @@ struct catalogue_state { ...@@ -340,23 +322,23 @@ struct catalogue_state {
new_info->ions = std::move(new_ions); new_info->ions = std::move(new_ions);
deriv.derived_info = std::move(new_info); deriv.derived_info = std::move(new_info);
return deriv; return std::move(deriv);
} }
// Implicit derivation. // Implicit derivation.
hopefully<derivation> derive(const std::string& name) const { hopefully<derivation> derive(const std::string& name) const {
if (defined(name)) { if (defined(name)) {
return make_exception_ptr(duplicate_mechanism(name)); return unexpected_exception_ptr(duplicate_mechanism(name));
} }
auto i = name.find_last_of('/'); auto i = name.find_last_of('/');
if (i==std::string::npos) { if (i==std::string::npos) {
return make_exception_ptr(no_such_mechanism(name)); return unexpected_exception_ptr(no_such_mechanism(name));
} }
std::string base = name.substr(0, i); std::string base = name.substr(0, i);
if (!defined(base)) { if (!defined(base)) {
return make_exception_ptr(no_such_mechanism(base)); return unexpected_exception_ptr(no_such_mechanism(base));
} }
std::string suffix = name.substr(i+1); std::string suffix = name.substr(i+1);
...@@ -385,7 +367,7 @@ struct catalogue_state { ...@@ -385,7 +367,7 @@ struct catalogue_state {
auto eq = assign.find('='); auto eq = assign.find('=');
if (eq==std::string::npos) { if (eq==std::string::npos) {
if (!single_ion) { if (!single_ion) {
return make_exception_ptr(invalid_ion_remap(assign)); return unexpected_exception_ptr(invalid_ion_remap(assign));
} }
k = info->ions.begin()->first; k = info->ions.begin()->first;
...@@ -403,7 +385,7 @@ struct catalogue_state { ...@@ -403,7 +385,7 @@ struct catalogue_state {
char* end = 0; char* end = 0;
double v_value = std::strtod(v.c_str(), &end); double v_value = std::strtod(v.c_str(), &end);
if (!end || *end) { if (!end || *end) {
return make_exception_ptr(invalid_parameter_value(name, k, v)); return unexpected_exception_ptr(invalid_parameter_value(name, k, v));
} }
global_params.push_back({k, v_value}); global_params.push_back({k, v_value});
} }
...@@ -420,9 +402,9 @@ struct catalogue_state { ...@@ -420,9 +402,9 @@ struct catalogue_state {
if (!defined(name)) { if (!defined(name)) {
implicit_deriv = derive(name); implicit_deriv = derive(name);
if (!implicit_deriv) { if (!implicit_deriv) {
return implicit_deriv.second(); return unexpected(implicit_deriv.error());
} }
impl_name = &implicit_deriv.first().parent; impl_name = &implicit_deriv->parent;
} }
for (;;) { for (;;) {
...@@ -437,7 +419,7 @@ struct catalogue_state { ...@@ -437,7 +419,7 @@ struct catalogue_state {
impl_name = &p->parent; impl_name = &p->parent;
} }
else { else {
return make_exception_ptr(no_such_implementation(name)); return unexpected_exception_ptr(no_such_implementation(name));
} }
} }
} }
...@@ -482,10 +464,10 @@ struct catalogue_state { ...@@ -482,10 +464,10 @@ struct catalogue_state {
util::optional<derivation> implicit_deriv; util::optional<derivation> implicit_deriv;
if (!defined(name)) { if (!defined(name)) {
if (auto deriv = derive(name)) { if (auto deriv = derive(name)) {
implicit_deriv = std::move(deriv.first()); implicit_deriv = std::move(deriv.value());
} }
else { else {
return deriv.second(); return unexpected(deriv.error());
} }
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <arbor/morph/mprovider.hpp> #include <arbor/morph/mprovider.hpp>
#include <arbor/morph/primitives.hpp> #include <arbor/morph/primitives.hpp>
#include <arbor/morph/region.hpp> #include <arbor/morph/region.hpp>
#include <arbor/util/expected.hpp>
namespace arb { namespace arb {
...@@ -35,19 +36,19 @@ void mprovider::init() { ...@@ -35,19 +36,19 @@ void mprovider::init() {
// label_dict_ptr will be null, and concrete regions/locsets will only be retrieved // label_dict_ptr will be null, and concrete regions/locsets will only be retrieved
// from the maps established during initialization. // from the maps established during initialization.
template <typename RegOrLocMap, typename LabelDictMap, typename Err> template <typename RegOrLocMap, typename LabelDictMap>
static const auto& try_lookup(const mprovider& provider, const std::string& name, RegOrLocMap& map, const LabelDictMap* dict_ptr, Err errval) { static const auto& try_lookup(const mprovider& provider, const std::string& name, RegOrLocMap& map, const LabelDictMap* dict_ptr) {
auto it = map.find(name); auto it = map.find(name);
if (it==map.end()) { if (it==map.end()) {
if (dict_ptr) { if (dict_ptr) {
map.emplace(name, errval); map.emplace(name, util::unexpect);
auto it = dict_ptr->find(name); auto it = dict_ptr->find(name);
if (it==dict_ptr->end()) { if (it==dict_ptr->end()) {
throw unbound_name(name); throw unbound_name(name);
} }
return (map[name] = thingify(it->second, provider)).first(); return (map[name] = thingify(it->second, provider)).value();
} }
else { else {
throw unbound_name(name); throw unbound_name(name);
...@@ -57,18 +58,18 @@ static const auto& try_lookup(const mprovider& provider, const std::string& name ...@@ -57,18 +58,18 @@ static const auto& try_lookup(const mprovider& provider, const std::string& name
throw circular_definition(name); throw circular_definition(name);
} }
else { else {
return it->second.first(); return it->second.value();
} }
} }
const mextent& mprovider::region(const std::string& name) const { const mextent& mprovider::region(const std::string& name) const {
const auto* regions_ptr = label_dict_ptr? &(label_dict_ptr->regions()): nullptr; const auto* regions_ptr = label_dict_ptr? &(label_dict_ptr->regions()): nullptr;
return try_lookup(*this, name, regions_, regions_ptr, circular_def{}); return try_lookup(*this, name, regions_, regions_ptr);
} }
const mlocation_list& mprovider::locset(const std::string& name) const { const mlocation_list& mprovider::locset(const std::string& name) const {
const auto* locsets_ptr = label_dict_ptr? &(label_dict_ptr->locsets()): nullptr; const auto* locsets_ptr = label_dict_ptr? &(label_dict_ptr->locsets()): nullptr;
return try_lookup(*this, name, locsets_, locsets_ptr, circular_def{}); return try_lookup(*this, name, locsets_, locsets_ptr);
} }
......
...@@ -9,11 +9,11 @@ ...@@ -9,11 +9,11 @@
#include <type_traits> #include <type_traits>
#include <arbor/assert.hpp> #include <arbor/assert.hpp>
#include <arbor/util/uninitialized.hpp>
#include <util/iterutil.hpp> #include "util/iterutil.hpp"
#include <util/meta.hpp> #include "util/meta.hpp"
#include <util/range.hpp> #include "util/range.hpp"
namespace arb { namespace arb {
namespace util { namespace util {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
#include <stdexcept> #include <stdexcept>
#include <type_traits> #include <type_traits>
#include <arbor/util/either.hpp> #include <arbor/util/expected.hpp>
#include "util/meta.hpp" #include "util/meta.hpp"
#include "util/partition_iterator.hpp" #include "util/partition_iterator.hpp"
...@@ -52,7 +52,7 @@ public: ...@@ -52,7 +52,7 @@ public:
void validate() const { void validate() const {
auto ok = is_valid(); auto ok = is_valid();
if (!ok) { if (!ok) {
throw invalid_partition(ok.second()); throw invalid_partition(ok.error());
} }
} }
...@@ -86,13 +86,11 @@ public: ...@@ -86,13 +86,11 @@ public:
} }
private: private:
either<bool, std::string> is_valid() const { expected<void, std::string> is_valid() const {
if (!std::is_sorted(left.get(), right.get())) { if (!std::is_sorted(left.get(), right.get())) {
return std::string("offsets are not monotonically increasing"); return unexpected(std::string("offsets are not monotonically increasing"));
}
else {
return true;
} }
return {};
} }
}; };
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#include <type_traits> #include <type_traits>
#include <arbor/util/either.hpp> #include <arbor/util/variant.hpp>
#include "util/meta.hpp" #include "util/meta.hpp"
...@@ -28,26 +28,26 @@ struct iterator_category_select<I,S,true> { ...@@ -28,26 +28,26 @@ struct iterator_category_select<I,S,true> {
template <typename I, typename S> template <typename I, typename S>
class sentinel_iterator { class sentinel_iterator {
arb::util::either<I, S> e_; arb::util::variant<I, S> e_;
I& iter() { I& iter() {
arb_assert(!is_sentinel()); arb_assert(!is_sentinel());
return e_.template unsafe_get<0>(); return get<0>(e_);
} }
const I& iter() const { const I& iter() const {
arb_assert(!is_sentinel()); arb_assert(!is_sentinel());
return e_.template unsafe_get<0>(); return get<0>(e_);
} }
S& sentinel() { S& sentinel() {
arb_assert(is_sentinel()); arb_assert(is_sentinel());
return e_.template unsafe_get<1>(); return get<1>(e_);
} }
const S& sentinel() const { const S& sentinel() const {
arb_assert(is_sentinel()); arb_assert(is_sentinel());
return e_.template unsafe_get<1>(); return get<1>(e_);
} }
public: public:
......
...@@ -9,9 +9,11 @@ ...@@ -9,9 +9,11 @@
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <util/iterutil.hpp> #include <arbor/util/uninitialized.hpp>
#include <util/meta.hpp>
#include <util/range.hpp> #include "util/iterutil.hpp"
#include "util/meta.hpp"
#include "util/range.hpp"
namespace arb { namespace arb {
namespace util { namespace util {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <arbor/arbexcept.hpp> #include <arbor/arbexcept.hpp>
#include <arbor/util/either.hpp> #include <arbor/util/expected.hpp>
#include "strprintf.hpp" #include "strprintf.hpp"
...@@ -57,12 +57,12 @@ template <typename T, typename E> ...@@ -57,12 +57,12 @@ template <typename T, typename E>
struct hopefully { struct hopefully {
using value_type = T; using value_type = T;
using error_type = E; using error_type = E;
arb::util::either<value_type, error_type> state; arb::util::expected<value_type, error_type> state;
hopefully(const hopefully&) = default; hopefully(const hopefully&) = default;
hopefully(value_type x): state(std::move(x)) {} hopefully(value_type x): state(std::move(x)) {}
hopefully(error_type x): state(std::move(x)) {} hopefully(error_type x): state(arb::util::unexpect, std::move(x)) {}
const value_type& operator*() const { const value_type& operator*() const {
return try_get(); return try_get();
...@@ -83,9 +83,9 @@ struct hopefully { ...@@ -83,9 +83,9 @@ struct hopefully {
const error_type& error() const { const error_type& error() const {
try { try {
return state.template get<1>(); return state.error();
} }
catch(arb::util::either_invalid_access& e) { catch(arb::util::bad_expected_access<void>& e) {
throw arb::arbor_internal_error( throw arb::arbor_internal_error(
"Attempt to get an error from a valid hopefully wrapper."); "Attempt to get an error from a valid hopefully wrapper.");
} }
...@@ -95,9 +95,9 @@ private: ...@@ -95,9 +95,9 @@ private:
const value_type& try_get() const { const value_type& try_get() const {
try { try {
return state.template get<0>(); return state.value();
} }
catch(arb::util::either_invalid_access& e) { catch(arb::util::bad_expected_access<void>& e) {
throw arbor_internal_error(util::pprintf( throw arbor_internal_error(util::pprintf(
"Attempt to unwrap a hopefully with error state '{}'", "Attempt to unwrap a hopefully with error state '{}'",
error().message)); error().message));
...@@ -105,9 +105,9 @@ private: ...@@ -105,9 +105,9 @@ private:
} }
value_type& try_get() { value_type& try_get() {
try { try {
return state.template get<0>(); return state.value();
} }
catch(arb::util::either_invalid_access& e) { catch(arb::util::bad_expected_access<void>& e) {
throw arb::arbor_internal_error(util::pprintf( throw arb::arbor_internal_error(util::pprintf(
"Attempt to unwrap a hopefully with error state '{}'", "Attempt to unwrap a hopefully with error state '{}'",
error().message)); error().message));
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <arbor/util/either.hpp>
#include <arbor/util/variant.hpp> #include <arbor/util/variant.hpp>
namespace pyarb { namespace pyarb {
......
...@@ -95,11 +95,11 @@ set(unit_sources ...@@ -95,11 +95,11 @@ set(unit_sources
test_domain_decomposition.cpp test_domain_decomposition.cpp
test_dry_run_context.cpp test_dry_run_context.cpp
test_double_buffer.cpp test_double_buffer.cpp
test_either.cpp
test_event_binner.cpp test_event_binner.cpp
test_event_delivery.cpp test_event_delivery.cpp
test_event_generators.cpp test_event_generators.cpp
test_event_queue.cpp test_event_queue.cpp
test_expected.cpp
test_filter.cpp test_filter.cpp
test_fvm_layout.cpp test_fvm_layout.cpp
test_fvm_lowered.cpp test_fvm_lowered.cpp
......
#include <typeinfo>
#include <array>
#include <algorithm>
#include <arbor/util/either.hpp>
#include "../gtest.h"
// TODO: coverage!
using namespace arb::util;
TEST(either, basic) {
either<int, std::string> e0(17);
EXPECT_TRUE(e0);
EXPECT_EQ(17, e0.get<0>());
EXPECT_EQ(e0.unsafe_get<0>(), e0.get<0>());
EXPECT_EQ(e0.unsafe_get<0>(), e0.first());
EXPECT_THROW(e0.get<1>(), either_invalid_access);
either<int, std::string> e1("seventeen");
EXPECT_FALSE(e1);
EXPECT_EQ("seventeen", e1.get<1>());
EXPECT_EQ(e1.unsafe_get<1>(), e1.get<1>());
EXPECT_EQ(e1.unsafe_get<1>(), e1.second());
EXPECT_THROW(e1.get<0>(), either_invalid_access);
e0 = e1;
EXPECT_EQ("seventeen", e0.get<1>());
EXPECT_THROW(e0.get<0>(), either_invalid_access);
e0 = 19;
EXPECT_EQ(19, e0.get<0>());
}
struct no_copy {
int value;
no_copy(): value(23) {}
explicit no_copy(int v): value(v) {}
no_copy(const no_copy&) = delete;
no_copy(no_copy&&) = default;
no_copy& operator=(const no_copy&) = delete;
no_copy& operator=(no_copy&&) = default;
};
TEST(either, no_copy) {
either<no_copy, std::string> e0(no_copy{17});
EXPECT_TRUE(e0);
either<no_copy, std::string> e1(std::move(e0));
EXPECT_TRUE(e1);
either<no_copy, std::string> e2;
EXPECT_TRUE(e2);
EXPECT_EQ(23, e2.get<0>().value);
e2 = std::move(e1);
EXPECT_TRUE(e2);
EXPECT_EQ(17, e2.get<0>().value);
}
#include "../gtest.h"
#include <utility>
#include <vector>
#include <arbor/util/expected.hpp>
#include "common.hpp"
using namespace arb::util;
using std::in_place;
TEST(expected, ctors) {
// Test constructors, verify against bool conversion and
// access via value() and error().
struct int3 {
int v = 3;
int3() = default;
int3(int a, int b, int c): v(a+b+c) {}
};
{
// Default construction.
expected<int3, int3> x;
EXPECT_TRUE(x);
EXPECT_EQ(3, x.value().v);
}
{
// Default void construction.
expected<void, int3> x;
EXPECT_TRUE(x);
}
{
// In-place construction.
expected<int3, int3> x(in_place, 1, 2, 3);
EXPECT_TRUE(x);
EXPECT_EQ(6, x.value().v);
}
{
// From-value construction.
int3 v;
v.v = 19;
expected<int3, int3> x(v);
EXPECT_TRUE(x);
EXPECT_EQ(19, x.value().v);
}
{
// From-unexpected construction.
int3 v;
v.v = 19;
expected<int3, int3> x{unexpected(v)};
EXPECT_FALSE(x);
EXPECT_EQ(19, x.error().v);
EXPECT_THROW(x.value(), bad_expected_access<int3>);
}
{
// From-unexpected void construction.
int3 v;
v.v = 19;
expected<void, int3> x{unexpected(v)};
EXPECT_FALSE(x);
EXPECT_EQ(19, x.error().v);
}
{
// In-place unexpected construction.
expected<int3, int3> x(unexpect, 1, 2, 3);
EXPECT_FALSE(x);
EXPECT_EQ(6, x.error().v);
EXPECT_THROW(x.value(), bad_expected_access<int3>);
}
{
// In-place void unexpected construction.
expected<void, int3> x(unexpect, 1, 2, 3);
EXPECT_FALSE(x);
EXPECT_EQ(6, x.error().v);
}
{
// Conversion from other expect.
struct X {};
struct Y {};
struct Z {
int v = 0;
Z(const X&): v(1) {}
Z(const Y&): v(2) {}
Z(X&&): v(-1) {}
Z(Y&&): v(-2) {}
};
expected<X, Y> x;
expected<Z, Z> y(x);
EXPECT_TRUE(y);
EXPECT_EQ(1, y.value().v);
expected<Z, Z> my(std::move(x));
EXPECT_TRUE(my);
EXPECT_EQ(-1, my.value().v);
expected<X, Y> xu(unexpect);
expected<Z, Z> yu(xu);
EXPECT_FALSE(yu);
EXPECT_EQ(2, yu.error().v);
expected<Z, Z> myu(std::move(xu));
EXPECT_FALSE(myu);
EXPECT_EQ(-2, myu.error().v);
}
}
TEST(expected, assignment) {
{
expected<int, int> x(10), y(12), z(unexpect, 20);
EXPECT_EQ(12, (x=y).value());
EXPECT_EQ(20, (x=z).error());
expected<void, int> u, v, w(unexpect, 30);
EXPECT_TRUE((u=v).has_value());
EXPECT_EQ(30, (u=w).error());
}
{
struct X {
X(): v(0) {}
X(const int& a): v(10*a) {}
X(int&& a): v(20*a) {}
int v;
};
expected<X, int> y;
EXPECT_EQ(20, (y=1).value().v);
int a = 3;
EXPECT_EQ(30, (y=a).value().v);
expected<int, X> z;
EXPECT_EQ(20, (z=unexpected(1)).error().v);
unexpected<int> b(3);
EXPECT_EQ(30, (z=b).error().v);
expected<void, X> v;
EXPECT_EQ(20, (v=unexpected(1)).error().v);
EXPECT_EQ(30, (v=b).error().v);
}
}
TEST(expected, emplace) {
// Check we're forwarding properly...
struct X {
X(): v(0) {}
X(const int& a, int b): v(10*a + b) {}
X(int&& a, int b): v(20*a + b) {}
int v;
};
expected<X, bool> ex;
EXPECT_TRUE(ex);
EXPECT_EQ(0, ex.value().v);
int i = 3, j = 4;
ex.emplace(i, j);
EXPECT_TRUE(ex);
EXPECT_EQ(34, ex.value().v);
ex.emplace(3, j);
EXPECT_TRUE(ex);
EXPECT_EQ(64, ex.value().v);
// Should also work if ex was in error state.
expected<X, bool> ux(unexpect);
EXPECT_FALSE(ux);
ux.emplace(4, 1);
EXPECT_TRUE(ux);
EXPECT_EQ(81, ux.value().v);
}
TEST(expected, equality) {
{
// non-void value expected comparisons:
expected<int, int> ex1(1), ux1(unexpect, 1), ex2(2), ux2(unexpect, 2);
expected<int, int> x(ex1);
EXPECT_TRUE(x==ex1);
EXPECT_TRUE(ex1==x);
EXPECT_FALSE(x!=ex1);
EXPECT_FALSE(ex1!=x);
EXPECT_FALSE(x==ex2);
EXPECT_FALSE(ex2==x);
EXPECT_TRUE(x!=ex2);
EXPECT_TRUE(ex2!=x);
EXPECT_FALSE(x==ux1);
EXPECT_FALSE(ux1==x);
EXPECT_TRUE(x!=ux1);
EXPECT_TRUE(ux1!=x);
EXPECT_FALSE(ux1==ux2);
EXPECT_FALSE(ux2==ux1);
EXPECT_TRUE(ux1!=ux2);
EXPECT_TRUE(ux2!=ux1);
}
{
// non-void comparison against values and unexpected.
expected<int, int> x(10);
EXPECT_TRUE(x==10);
EXPECT_TRUE(10==x);
EXPECT_FALSE(x!=10);
EXPECT_FALSE(10!=x);
EXPECT_FALSE(x==unexpected(10));
EXPECT_FALSE(unexpected(10)==x);
EXPECT_TRUE(x!=unexpected(10));
EXPECT_TRUE(unexpected(10)!=x);
x = unexpected(10);
EXPECT_FALSE(x==10);
EXPECT_FALSE(10==x);
EXPECT_TRUE(x!=10);
EXPECT_TRUE(10!=x);
EXPECT_TRUE(x==unexpected(10));
EXPECT_TRUE(unexpected(10)==x);
EXPECT_FALSE(x!=unexpected(10));
EXPECT_FALSE(unexpected(10)!=x);
}
{
// void value expected comparisons:
expected<void, int> ev, uv1(unexpect, 1), uv2(unexpect, 2);
expected<void, int> x(ev);
EXPECT_TRUE(x==ev);
EXPECT_TRUE(ev==x);
EXPECT_FALSE(x!=ev);
EXPECT_FALSE(ev!=x);
EXPECT_FALSE(x==uv1);
EXPECT_FALSE(uv1==x);
EXPECT_TRUE(x!=uv1);
EXPECT_TRUE(uv1!=x);
EXPECT_FALSE(uv1==uv2);
EXPECT_FALSE(uv2==uv1);
EXPECT_TRUE(uv1!=uv2);
EXPECT_TRUE(uv2!=uv1);
}
{
// void value but difference unexpected types:
expected<void, int> uvi(unexpect);
expected<void, double> uvd(unexpect, 3.);
EXPECT_FALSE(uvi==uvd);
EXPECT_FALSE(uvd==uvi);
uvi = expected<void, int>();
ASSERT_TRUE(uvi);
EXPECT_FALSE(uvi==uvd);
EXPECT_FALSE(uvd==uvi);
uvd = expected<void, double>();
ASSERT_TRUE(uvd);
EXPECT_TRUE(uvi==uvd);
EXPECT_TRUE(uvd==uvi);
}
{
// void comparison against unexpected.
expected<void, int> x;
EXPECT_TRUE(x);
EXPECT_FALSE(x==unexpected(10));
EXPECT_FALSE(unexpected(10)==x);
EXPECT_TRUE(x!=unexpected(10));
EXPECT_TRUE(unexpected(10)!=x);
x = unexpected<int>(10);
EXPECT_FALSE(x);
EXPECT_TRUE(x==unexpected(10));
EXPECT_TRUE(unexpected(10)==x);
EXPECT_FALSE(x!=unexpected(10));
EXPECT_FALSE(unexpected(10)!=x);
}
}
TEST(expected, value_or) {
expected<double, char> a(2.0), b(unexpect, 'x');
EXPECT_EQ(2.0, a.value_or(1));
EXPECT_EQ(1.0, b.value_or(1));
}
namespace {
struct Xswap {
explicit Xswap(int val, int& r): val(val), n_swap_ptr(&r) {}
int val;
int* n_swap_ptr;
void swap(Xswap& other) {
++*n_swap_ptr;
std::swap(val, other.val);
}
friend void swap(Xswap& x1, Xswap& x2) noexcept { x1.swap(x2); }
};
struct swap_can_throw {
friend void swap(swap_can_throw& s1, swap_can_throw& s2) noexcept(false) {}
};
}
TEST(expected, swap) {
int swaps = 0;
expected<Xswap, int> x1(in_place, -1, swaps), x2(in_place, -2, swaps);
using std::swap;
swap(x1, x2);
EXPECT_EQ(-2, x1->val);
EXPECT_EQ(-1, x2->val);
EXPECT_EQ(1, swaps);
swaps = 0;
expected<Xswap, int> x3(unexpect, 4);
swap(x1, x3);
EXPECT_EQ(4, x1.error());
EXPECT_EQ(-2, x3->val);
EXPECT_EQ(0, swaps); // Xswap is moved, not swapped.
swaps = 0;
unexpected<Xswap> u1(in_place, -1, swaps), u2(in_place, -2, swaps);
swap(u1, u2);
EXPECT_EQ(-2, u1.value().val);
EXPECT_EQ(-1, u2.value().val);
EXPECT_EQ(1, swaps);
EXPECT_TRUE(std::is_nothrow_swappable<unexpected<Xswap>>::value);
EXPECT_FALSE(std::is_nothrow_swappable<unexpected<swap_can_throw>>::value);
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment