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