From 74e911e6b61c4b9689095668fbdd030bb46ab60e Mon Sep 17 00:00:00 2001
From: Sam Yates <halfflat@gmail.com>
Date: Mon, 14 Sep 2020 21:46:39 +0200
Subject: [PATCH] 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.
---
arbor/include/arbor/morph/mprovider.hpp | 6 +-
arbor/include/arbor/util/either.hpp | 384 -----------------
arbor/include/arbor/util/expected.hpp | 524 ++++++++++++++++++++++++
arbor/include/arbor/util/optional.hpp | 15 +-
arbor/include/arbor/util/variant.hpp | 6 +-
arbor/mechcat.cpp | 104 ++---
arbor/morph/mprovider.cpp | 15 +-
arbor/util/filter.hpp | 8 +-
arbor/util/partition.hpp | 12 +-
arbor/util/sentinel.hpp | 12 +-
arbor/util/transform.hpp | 8 +-
python/error.hpp | 18 +-
python/s_expr.hpp | 1 -
test/unit/CMakeLists.txt | 2 +-
test/unit/test_either.cpp | 65 ---
test/unit/test_expected.cpp | 348 ++++++++++++++++
16 files changed, 972 insertions(+), 556 deletions(-)
delete mode 100644 arbor/include/arbor/util/either.hpp
create mode 100644 arbor/include/arbor/util/expected.hpp
delete mode 100644 test/unit/test_either.cpp
create mode 100644 test/unit/test_expected.cpp
diff --git a/arbor/include/arbor/morph/mprovider.hpp b/arbor/include/arbor/morph/mprovider.hpp
index 0169df17..7cc3e126 100644
--- a/arbor/include/arbor/morph/mprovider.hpp
+++ b/arbor/include/arbor/morph/mprovider.hpp
@@ -6,7 +6,7 @@
#include <arbor/morph/embed_pwlin.hpp>
#include <arbor/morph/primitives.hpp>
#include <arbor/morph/label_dict.hpp>
-#include <arbor/util/either.hpp>
+#include <arbor/util/expected.hpp>
namespace arb {
@@ -34,8 +34,8 @@ private:
struct circular_def {};
// 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::either<mlocation_list, circular_def>> locsets_;
+ mutable std::unordered_map<std::string, util::expected<mextent, circular_def>> regions_;
+ mutable std::unordered_map<std::string, util::expected<mlocation_list, circular_def>> locsets_;
// Non-null only during initialization phase.
mutable const label_dict* label_dict_ptr;
diff --git a/arbor/include/arbor/util/either.hpp b/arbor/include/arbor/util/either.hpp
deleted file mode 100644
index 08cc6612..00000000
--- a/arbor/include/arbor/util/either.hpp
+++ /dev/null
@@ -1,384 +0,0 @@
-#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
diff --git a/arbor/include/arbor/util/expected.hpp b/arbor/include/arbor/util/expected.hpp
new file mode 100644
index 00000000..be9dba14
--- /dev/null
+++ b/arbor/include/arbor/util/expected.hpp
@@ -0,0 +1,524 @@
+#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
diff --git a/arbor/include/arbor/util/optional.hpp b/arbor/include/arbor/util/optional.hpp
index c314f3a8..96921676 100644
--- a/arbor/include/arbor/util/optional.hpp
+++ b/arbor/include/arbor/util/optional.hpp
@@ -14,8 +14,6 @@
*
* 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).
*
* Additional/differing functionality:
@@ -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(); }
const_reference ref() const { return data.cref(); }
@@ -208,6 +213,12 @@ struct optional: detail::optional_base<X> {
optional(X&& 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()) {}
template <typename T>
diff --git a/arbor/include/arbor/util/variant.hpp b/arbor/include/arbor/util/variant.hpp
index 1f3ca0ca..ecb724a1 100644
--- a/arbor/include/arbor/util/variant.hpp
+++ b/arbor/include/arbor/util/variant.hpp
@@ -172,7 +172,7 @@ struct variant_dynamic_impl<> {
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{};
}
@@ -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) {
- *reinterpret_cast<H*>(data) = std::move(*reinterpret_cast<const H*>(from));
+ *reinterpret_cast<H*>(data) = std::move(*reinterpret_cast<H*>(from));
}
else {
variant_dynamic_impl<T...>::move_assign(i-1, data, from);
diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp
index 4be6af12..ab30845c 100644
--- a/arbor/mechcat.cpp
+++ b/arbor/mechcat.cpp
@@ -6,7 +6,7 @@
#include <arbor/arbexcept.hpp>
#include <arbor/mechcat.hpp>
-#include <arbor/util/either.hpp>
+#include <arbor/util/expected.hpp>
#include "util/maputil.hpp"
@@ -53,15 +53,14 @@
* mechanism.
*
* 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.
*/
namespace arb {
using util::value_by_key;
-using util::optional;
-using util::nullopt;
+using util::unexpected;
using std::make_unique;
using std::make_exception_ptr;
@@ -72,42 +71,26 @@ template <typename V>
using string_map = std::unordered_map<std::string, V>;
template <typename T>
-struct hopefully_typemap {
- 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;
+using hopefully = util::expected<T, std::exception_ptr>;
+namespace {
// Convert hopefully<T> to T or throw.
-
template <typename T>
-const T& value(const util::either<T, std::exception_ptr>& x) {
- if (!x) {
- std::rethrow_exception(x.second());
- }
- return x.first();
+const T& value(const hopefully<T>& x) {
+ if (!x) std::rethrow_exception(x.error());
+ return x.value();
}
template <typename T>
-T value(util::either<T, std::exception_ptr>&& x) {
- if (!x) {
- std::rethrow_exception(x.second());
- }
- return std::move(x.first());
+T value(hopefully<T>&& x) {
+ if (!x) std::rethrow_exception(std::move(x).error());
+ return std::move(x).value();
}
-void value(const hopefully<void>& x) {
- if (!x) {
- std::rethrow_exception(x.second());
- }
-}
+// Conveniently make an unexpected exception_ptr:
+template <typename X>
+auto unexpected_exception_ptr(X x) { return unexpected(make_exception_ptr(std::move(x))); }
+} // anonymous namespace
struct derivation {
std::string parent;
@@ -187,17 +170,16 @@ struct catalogue_state {
// 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) {
if (auto fptr = fingerprint_ptr(name)) {
- if (mech->fingerprint()!=*fptr.first()) {
- return make_exception_ptr(fingerprint_mismatch(name));
+ if (mech->fingerprint()!=*fptr.value()) {
+ return unexpected_exception_ptr(fingerprint_mismatch(name));
}
impl_map_[name][tidx] = std::move(mech);
+ return {};
}
else {
- return fptr.second();
+ return unexpected(fptr.error());
}
-
- return {};
}
// Remove mechanism and its derivations and implementations.
@@ -234,10 +216,10 @@ struct catalogue_state {
return *(p->get());
}
else if (auto deriv = derive(name)) {
- return *(deriv.first().derived_info.get());
+ return *(deriv->derived_info.get());
}
else {
- return deriv.second();
+ return unexpected(deriv.error());
}
}
@@ -249,10 +231,10 @@ struct catalogue_state {
if (!defined(name)) {
if ((implicit_deriv = derive(name))) {
- base = &implicit_deriv.first().parent;
+ base = &implicit_deriv->parent;
}
else {
- return implicit_deriv.second();
+ return unexpected(implicit_deriv.error());
}
}
@@ -274,10 +256,10 @@ struct catalogue_state {
const std::vector<std::pair<std::string, std::string>>& ion_remap_vec) const
{
if (defined(name)) {
- return make_exception_ptr(duplicate_mechanism(name));
+ return unexpected_exception_ptr(duplicate_mechanism(name));
}
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());
@@ -285,10 +267,10 @@ struct catalogue_state {
mechanism_info_ptr new_info;
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 {
- return parent_info.second();
+ return unexpected(parent_info.error());
}
// Update global parameter values in info for derived mechanism.
@@ -299,11 +281,11 @@ struct catalogue_state {
if (auto p = value_by_key(new_info->globals, param)) {
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 {
- return make_exception_ptr(no_such_parameter(name, param));
+ return unexpected_exception_ptr(no_such_parameter(name, param));
}
deriv.globals[param] = value;
@@ -312,7 +294,7 @@ struct catalogue_state {
for (const auto& kv: ion_remap_vec) {
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 {
for (const auto& kv: new_info->ions) {
if (auto new_ion = value_by_key(ion_remap_map, kv.first)) {
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 {
@@ -330,7 +312,7 @@ struct catalogue_state {
// (find offending remap to report in exception)
for (const auto& entry: ion_remap_map) {
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");
@@ -340,23 +322,23 @@ struct catalogue_state {
new_info->ions = std::move(new_ions);
deriv.derived_info = std::move(new_info);
- return deriv;
+ return std::move(deriv);
}
// Implicit derivation.
hopefully<derivation> derive(const std::string& name) const {
if (defined(name)) {
- return make_exception_ptr(duplicate_mechanism(name));
+ return unexpected_exception_ptr(duplicate_mechanism(name));
}
auto i = name.find_last_of('/');
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);
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);
@@ -385,7 +367,7 @@ struct catalogue_state {
auto eq = assign.find('=');
if (eq==std::string::npos) {
if (!single_ion) {
- return make_exception_ptr(invalid_ion_remap(assign));
+ return unexpected_exception_ptr(invalid_ion_remap(assign));
}
k = info->ions.begin()->first;
@@ -403,7 +385,7 @@ struct catalogue_state {
char* end = 0;
double v_value = std::strtod(v.c_str(), &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});
}
@@ -420,9 +402,9 @@ struct catalogue_state {
if (!defined(name)) {
implicit_deriv = derive(name);
if (!implicit_deriv) {
- return implicit_deriv.second();
+ return unexpected(implicit_deriv.error());
}
- impl_name = &implicit_deriv.first().parent;
+ impl_name = &implicit_deriv->parent;
}
for (;;) {
@@ -437,7 +419,7 @@ struct catalogue_state {
impl_name = &p->parent;
}
else {
- return make_exception_ptr(no_such_implementation(name));
+ return unexpected_exception_ptr(no_such_implementation(name));
}
}
}
@@ -482,10 +464,10 @@ struct catalogue_state {
util::optional<derivation> implicit_deriv;
if (!defined(name)) {
if (auto deriv = derive(name)) {
- implicit_deriv = std::move(deriv.first());
+ implicit_deriv = std::move(deriv.value());
}
else {
- return deriv.second();
+ return unexpected(deriv.error());
}
}
diff --git a/arbor/morph/mprovider.cpp b/arbor/morph/mprovider.cpp
index 38e0226c..330fbf09 100644
--- a/arbor/morph/mprovider.cpp
+++ b/arbor/morph/mprovider.cpp
@@ -7,6 +7,7 @@
#include <arbor/morph/mprovider.hpp>
#include <arbor/morph/primitives.hpp>
#include <arbor/morph/region.hpp>
+#include <arbor/util/expected.hpp>
namespace arb {
@@ -35,19 +36,19 @@ void mprovider::init() {
// label_dict_ptr will be null, and concrete regions/locsets will only be retrieved
// from the maps established during initialization.
-template <typename RegOrLocMap, typename LabelDictMap, typename Err>
-static const auto& try_lookup(const mprovider& provider, const std::string& name, RegOrLocMap& map, const LabelDictMap* dict_ptr, Err errval) {
+template <typename RegOrLocMap, typename LabelDictMap>
+static const auto& try_lookup(const mprovider& provider, const std::string& name, RegOrLocMap& map, const LabelDictMap* dict_ptr) {
auto it = map.find(name);
if (it==map.end()) {
if (dict_ptr) {
- map.emplace(name, errval);
+ map.emplace(name, util::unexpect);
auto it = dict_ptr->find(name);
if (it==dict_ptr->end()) {
throw unbound_name(name);
}
- return (map[name] = thingify(it->second, provider)).first();
+ return (map[name] = thingify(it->second, provider)).value();
}
else {
throw unbound_name(name);
@@ -57,18 +58,18 @@ static const auto& try_lookup(const mprovider& provider, const std::string& name
throw circular_definition(name);
}
else {
- return it->second.first();
+ return it->second.value();
}
}
const mextent& mprovider::region(const std::string& name) const {
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 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);
}
diff --git a/arbor/util/filter.hpp b/arbor/util/filter.hpp
index 43f6fd88..32ea5516 100644
--- a/arbor/util/filter.hpp
+++ b/arbor/util/filter.hpp
@@ -9,11 +9,11 @@
#include <type_traits>
#include <arbor/assert.hpp>
+#include <arbor/util/uninitialized.hpp>
-#include <util/iterutil.hpp>
-#include <util/meta.hpp>
-#include <util/range.hpp>
-
+#include "util/iterutil.hpp"
+#include "util/meta.hpp"
+#include "util/range.hpp"
namespace arb {
namespace util {
diff --git a/arbor/util/partition.hpp b/arbor/util/partition.hpp
index f6d4f67c..14a50d68 100644
--- a/arbor/util/partition.hpp
+++ b/arbor/util/partition.hpp
@@ -5,7 +5,7 @@
#include <stdexcept>
#include <type_traits>
-#include <arbor/util/either.hpp>
+#include <arbor/util/expected.hpp>
#include "util/meta.hpp"
#include "util/partition_iterator.hpp"
@@ -52,7 +52,7 @@ public:
void validate() const {
auto ok = is_valid();
if (!ok) {
- throw invalid_partition(ok.second());
+ throw invalid_partition(ok.error());
}
}
@@ -86,13 +86,11 @@ public:
}
private:
- either<bool, std::string> is_valid() const {
+ expected<void, std::string> is_valid() const {
if (!std::is_sorted(left.get(), right.get())) {
- return std::string("offsets are not monotonically increasing");
- }
- else {
- return true;
+ return unexpected(std::string("offsets are not monotonically increasing"));
}
+ return {};
}
};
diff --git a/arbor/util/sentinel.hpp b/arbor/util/sentinel.hpp
index 9b1bf1a2..30d0696d 100644
--- a/arbor/util/sentinel.hpp
+++ b/arbor/util/sentinel.hpp
@@ -2,7 +2,7 @@
#include <type_traits>
-#include <arbor/util/either.hpp>
+#include <arbor/util/variant.hpp>
#include "util/meta.hpp"
@@ -28,26 +28,26 @@ struct iterator_category_select<I,S,true> {
template <typename I, typename S>
class sentinel_iterator {
- arb::util::either<I, S> e_;
+ arb::util::variant<I, S> e_;
I& iter() {
arb_assert(!is_sentinel());
- return e_.template unsafe_get<0>();
+ return get<0>(e_);
}
const I& iter() const {
arb_assert(!is_sentinel());
- return e_.template unsafe_get<0>();
+ return get<0>(e_);
}
S& sentinel() {
arb_assert(is_sentinel());
- return e_.template unsafe_get<1>();
+ return get<1>(e_);
}
const S& sentinel() const {
arb_assert(is_sentinel());
- return e_.template unsafe_get<1>();
+ return get<1>(e_);
}
public:
diff --git a/arbor/util/transform.hpp b/arbor/util/transform.hpp
index ed436973..747c5c95 100644
--- a/arbor/util/transform.hpp
+++ b/arbor/util/transform.hpp
@@ -9,9 +9,11 @@
#include <memory>
#include <type_traits>
-#include <util/iterutil.hpp>
-#include <util/meta.hpp>
-#include <util/range.hpp>
+#include <arbor/util/uninitialized.hpp>
+
+#include "util/iterutil.hpp"
+#include "util/meta.hpp"
+#include "util/range.hpp"
namespace arb {
namespace util {
diff --git a/python/error.hpp b/python/error.hpp
index c97dad43..8a650b8a 100644
--- a/python/error.hpp
+++ b/python/error.hpp
@@ -7,7 +7,7 @@
#include <pybind11/pybind11.h>
#include <arbor/arbexcept.hpp>
-#include <arbor/util/either.hpp>
+#include <arbor/util/expected.hpp>
#include "strprintf.hpp"
@@ -57,12 +57,12 @@ template <typename T, typename E>
struct hopefully {
using value_type = T;
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(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 {
return try_get();
@@ -83,9 +83,9 @@ struct hopefully {
const error_type& error() const {
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(
"Attempt to get an error from a valid hopefully wrapper.");
}
@@ -95,9 +95,9 @@ private:
const value_type& try_get() const {
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(
"Attempt to unwrap a hopefully with error state '{}'",
error().message));
@@ -105,9 +105,9 @@ private:
}
value_type& try_get() {
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(
"Attempt to unwrap a hopefully with error state '{}'",
error().message));
diff --git a/python/s_expr.hpp b/python/s_expr.hpp
index 8c4dd433..bf6998a1 100644
--- a/python/s_expr.hpp
+++ b/python/s_expr.hpp
@@ -4,7 +4,6 @@
#include <memory>
#include <vector>
-#include <arbor/util/either.hpp>
#include <arbor/util/variant.hpp>
namespace pyarb {
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index f988f32e..43d4eda8 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -95,11 +95,11 @@ set(unit_sources
test_domain_decomposition.cpp
test_dry_run_context.cpp
test_double_buffer.cpp
- test_either.cpp
test_event_binner.cpp
test_event_delivery.cpp
test_event_generators.cpp
test_event_queue.cpp
+ test_expected.cpp
test_filter.cpp
test_fvm_layout.cpp
test_fvm_lowered.cpp
diff --git a/test/unit/test_either.cpp b/test/unit/test_either.cpp
deleted file mode 100644
index 4d18d47c..00000000
--- a/test/unit/test_either.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#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);
-}
diff --git a/test/unit/test_expected.cpp b/test/unit/test_expected.cpp
new file mode 100644
index 00000000..1ca53f2b
--- /dev/null
+++ b/test/unit/test_expected.cpp
@@ -0,0 +1,348 @@
+#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);
+}
--
GitLab