diff --git a/src/util/counter.hpp b/src/util/counter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d071752807f53fc72324367d4e4ea87e0dc6c7be --- /dev/null +++ b/src/util/counter.hpp @@ -0,0 +1,95 @@ +#pragma once + +/* Present an integral value as an iterator, for integral-range 'containers' */ + +#include <cstddef> +#include <iterator> +#include <type_traits> + +namespace nest { +namespace mc { +namespace util { + +template <typename V, typename = typename std::enable_if<std::is_integral<V>::value>::type> +struct counter { + using difference_type = V; + using value_type = V; + using pointer = const V*; + using reference = const V&; + using iterator_category = std::random_access_iterator_tag; + + counter(): v_{} {} + counter(V v): v_{v} {} + + counter(const counter&) = default; + counter(counter&&) = default; + + counter& operator++() { + ++v_; + return *this; + } + + counter operator++(int) { + counter c(*this); + ++v_; + return c; + } + + counter& operator--() { + --v_; + return *this; + } + + counter operator--(int) { + counter c(*this); + --v_; + return c; + } + + counter& operator+=(difference_type n) { + v_ += n; + return *this; + } + + counter& operator-=(difference_type n) { + v_ -= n; + return *this; + } + + counter operator+(difference_type n) { + return counter(v_+n); + } + + friend counter operator+(difference_type n, counter x) { + return counter(n+x.v_); + } + + counter operator-(difference_type n) { + return counter(v_-n); + } + + difference_type operator-(counter x) const { + return v_-x.v_; + } + + value_type operator*() const { return v_; } + + value_type operator[](difference_type n) const { return v_+n; } + + bool operator==(counter x) const { return v_==x.v_; } + bool operator!=(counter x) const { return v_!=x.v_; } + bool operator<=(counter x) const { return v_<=x.v_; } + bool operator>=(counter x) const { return v_>=x.v_; } + bool operator<(counter x) const { return v_<x.v_; } + bool operator>(counter x) const { return v_>x.v_; } + + counter& operator=(const counter&) = default; + counter& operator=(counter&&) = default; + +private: + V v_; +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/debug.cpp b/src/util/debug.cpp index 51c646bbcb38b1722e40766f4886a957094bd1bb..f2f039708a860c7fcb9ec65db7345b608a019dd9 100644 --- a/src/util/debug.cpp +++ b/src/util/debug.cpp @@ -14,8 +14,11 @@ namespace util { std::mutex global_debug_cerr_mutex; -bool failed_assertion(const char* assertion, const char* file, - int line, const char* func) +bool abort_on_failed_assertion( + const char* assertion, + const char* file, + int line, + const char* func) { // Explicit flush, as we can't assume default buffering semantics on stderr/cerr, // and abort() might not flush streams. @@ -26,8 +29,13 @@ bool failed_assertion(const char* assertion, const char* file, return false; } -std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, - int line, const char* varlist) +failed_assertion_handler_t global_failed_assertion_handler = abort_on_failed_assertion; + +std::ostream& debug_emit_trace_leader( + std::ostream& out, + const char* file, + int line, + const char* varlist) { iosfmt_guard guard(out); diff --git a/src/util/debug.hpp b/src/util/debug.hpp index f258b37123755c7ec7a7324641249ed35290e8bb..be2b3d51e4d7bf9b5d4c5fb9b7c78855c5e4b4c5 100644 --- a/src/util/debug.hpp +++ b/src/util/debug.hpp @@ -10,7 +10,17 @@ namespace nest { namespace mc { namespace util { -bool failed_assertion(const char* assertion, const char* file, int line, const char* func); +using failed_assertion_handler_t = + bool (*)(const char* assertion, const char* file, int line, const char* func); + +bool abort_on_failed_assertion(const char* assertion, const char* file, int line, const char* func); +inline bool ignore_failed_assertion(const char*, const char*, int, const char*) { + return false; +} + +// defaults to abort_on_failed_assertion; +extern failed_assertion_handler_t global_failed_assertion_handler; + std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* varlist); inline void debug_emit(std::ostream& out) { @@ -66,7 +76,7 @@ void debug_emit_trace(const char* file, int line, const char* varlist, const Arg #define EXPECTS(condition) \ (void)((condition) || \ - nest::mc::util::failed_assertion(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME)) + nest::mc::util::global_failed_assertion_handler(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME)) #else #define EXPECTS(condition) #endif // def WITH_ASSERTIONS diff --git a/src/util/either.hpp b/src/util/either.hpp new file mode 100644 index 0000000000000000000000000000000000000000..36932c96e8be340638c98eb582d06ffa93450828 --- /dev/null +++ b/src/util/either.hpp @@ -0,0 +1,387 @@ +#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 "util/meta.hpp" +#include "util/uninitialized.hpp" + +namespace nest { +namespace mc { +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 = -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 = 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 = 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 = 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() -> decltype(getter<I>::ptr(which, *this)) { + return getter<I>::ptr(which, *this); + } + + template <std::size_t I> + auto ptr() const -> decltype(getter<I>::ptr(which, *this)) { + 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 mc +} // namespace nest diff --git a/src/util/iterutil.hpp b/src/util/iterutil.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6a9fd159ffe9f75eddf57f2adb5c4d42cb9e7861 --- /dev/null +++ b/src/util/iterutil.hpp @@ -0,0 +1,178 @@ +#pragma once + +/* + * Utilities and base classes to help with + * implementing iterators and iterator adaptors. + */ + +#include <iterator> +#include <memory> +#include <type_traits> +#include <utility> + +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +/* + * Return the iterator reachable from iter such that + * std::next(iter)==end + * + * Two implementations: the first applies generally, while the + * second is used when we can just return std::prev(end). + */ +template <typename I, typename E> +enable_if_t< + is_forward_iterator<I>::value && + (!is_bidirectional_iterator<E>::value || !std::is_constructible<I, E>::value), + I> +upto(I iter, E end) { + I j = iter; + while (j!=end) { + iter = j; + ++j; + } + return iter; +} + +template <typename I, typename E> +enable_if_t<is_bidirectional_iterator<E>::value && std::is_constructible<I, E>::value, I> +upto(I iter, E end) { + return iter==I{end}? iter: I{std::prev(end)}; +} + +/* + * Provide a proxy object for operator->() for iterator adaptors that + * present rvalues on dereference. + */ +template <typename V> +struct pointer_proxy: public V { + pointer_proxy(const V& v): V{v} {} + pointer_proxy(V&& v): V{std::move(v)} {} + const V* operator->() const { return this; } +}; + +/* + * Base class (using CRTP) for iterator adaptors that + * perform a transformation or present a proxy for + * an underlying iterator. + * + * Supplies default implementations for iterator concepts + * in terms of the derived class' methods and the + * inner iterator. + * + * Derived class must provide implementations for: + * operator*() + * operator[](difference_type) + * inner() // provides access to wrapped iterator + */ + +template <typename Derived, typename I> +class iterator_adaptor { +protected: + Derived& derived() { return static_cast<Derived&>(*this); } + const Derived& derived() const { return static_cast<const Derived&>(*this); } + +private: + // Access to inner iterator provided by derived class. + I& inner() { return derived().inner(); } + const I& inner() const { return derived().inner(); } + +public: + using value_type = typename std::iterator_traits<I>::value_type; + using difference_type = typename std::iterator_traits<I>::difference_type; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + using pointer = typename std::iterator_traits<I>::pointer; + using reference = typename std::iterator_traits<I>::reference; + + iterator_adaptor() = default; + + // forward and input iterator requirements + + I operator->() { return inner(); } + I operator->() const { return inner(); } + + Derived& operator++() { + ++inner(); + return derived(); + } + + Derived operator++(int) { + Derived c(derived()); + ++derived(); + return c; + } + + bool operator==(const Derived& x) const { + return inner()==x.inner(); + } + + bool operator!=(const Derived& x) const { + return !(derived()==x); + } + + // bidirectional iterator requirements + + Derived& operator--() { + --inner(); + return derived(); + } + + Derived operator--(int) { + Derived c(derived()); + --derived(); + return c; + } + + // random access iterator requirements + + Derived& operator+=(difference_type n) { + inner() += n; + return derived(); + } + + Derived operator+(difference_type n) const { + Derived c(derived()); + return c += n; + } + + friend Derived operator+(difference_type n, const Derived& x) { + return x+n; + } + + Derived& operator-=(difference_type n) { + inner() -= n; + return *this; + } + + Derived operator-(difference_type n) const { + Derived c(derived()); + return c -= n; + } + + difference_type operator-(const Derived& x) const { + return inner()-x.inner(); + } + + bool operator<(const Derived& x) const { + return inner()<x.inner(); + } + + bool operator<=(const Derived& x) const { + return derived()<x || derived()==x; + } + + bool operator>=(const Derived& x) const { + return !(derived()<x); + } + + bool operator>(const Derived& x) const { + return !(derived()<=x); + } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/meta.hpp b/src/util/meta.hpp index 2e22e31ceda0ff83588625486ec7a36161569733..7b1fb0d6f228175377242b607a111457ed90b414 100644 --- a/src/util/meta.hpp +++ b/src/util/meta.hpp @@ -2,6 +2,7 @@ /* Type utilities and convenience expressions. */ +#include <iterator> #include <type_traits> namespace nest { @@ -13,8 +14,45 @@ namespace util { template <typename T> using result_of_t = typename std::result_of<T>::type; -template <bool V> -using enable_if_t = typename std::enable_if<V>::type; +template <bool V, typename R = void> +using enable_if_t = typename std::enable_if<V, R>::type; + +template <class...> +using void_t = void; + +template <typename T> +using decay_t = typename std::decay<T>::type; + +template <typename X> +std::size_t size(const X& x) { return x.size(); } + +template <typename X, std::size_t N> +constexpr std::size_t size(X (&)[N]) { return N; } + +template <typename T> +constexpr auto cbegin(const T& c) -> decltype(std::begin(c)) { + return std::begin(c); +} + +template <typename T> +constexpr auto cend(const T& c) -> decltype(std::end(c)) { + return std::end(c); +} + +// Types associated with a container or sequence + +template <typename Seq> +struct sequence_traits { + using iterator = decltype(std::begin(std::declval<Seq&>())); + using const_iterator = decltype(cbegin(std::declval<Seq&>())); + using value_type = typename std::iterator_traits<iterator>::value_type; + using reference = typename std::iterator_traits<iterator>::reference; + using difference_type = typename std::iterator_traits<iterator>::difference_type; + using size_type = decltype(size(std::declval<Seq&>())); + // for use with heterogeneous ranges + using sentinel = decltype(std::end(std::declval<Seq&>())); + using const_sentinel = decltype(cend(std::declval<Seq&>())); +}; // Convenience short cuts @@ -26,10 +64,91 @@ template <typename T> using enable_if_move_constructible_t = enable_if_t<std::is_move_constructible<T>::value>; +template <typename T> +using enable_if_default_constructible_t = + enable_if_t<std::is_default_constructible<T>::value>; + template <typename... T> using enable_if_constructible_t = enable_if_t<std::is_constructible<T...>::value>; +template <typename T> +using enable_if_copy_assignable_t = + enable_if_t<std::is_copy_assignable<T>::value>; + +template <typename T> +using enable_if_move_assignable_t = + enable_if_t<std::is_move_assignable<T>::value>; + +// Iterator class test +// (might not be portable before C++17) + +template <typename T, typename = void> +struct is_iterator: public std::false_type {}; + +template <typename T> +struct is_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>>: + public std::true_type {}; + +template <typename T> +using is_iterator_t = typename is_iterator<T>::type; + +// Random access iterator test + +template <typename T, typename = void> +struct is_random_access_iterator: public std::false_type {}; + +template <typename T> +struct is_random_access_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_random_access_iterator_t = typename is_random_access_iterator<T>::type; + +// Bidirectional iterator test + +template <typename T, typename = void> +struct is_bidirectional_iterator: public std::false_type {}; + +template <typename T> +struct is_bidirectional_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::bidirectional_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_bidirectional_iterator_t = typename is_bidirectional_iterator<T>::type; + +// Forward iterator test + +template <typename T, typename = void> +struct is_forward_iterator: public std::false_type {}; + +template <typename T> +struct is_forward_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::bidirectional_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::forward_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_forward_iterator_t = typename is_forward_iterator<T>::type; } // namespace util } // namespace mc diff --git a/src/util/optional.hpp b/src/util/optional.hpp index 01e055c1730812eb04c7b329abc80a71a21f075c..40b40d7c3ff27352638c789575172a5cd5fee8d4 100644 --- a/src/util/optional.hpp +++ b/src/util/optional.hpp @@ -38,16 +38,6 @@ struct optional_unset_error: std::runtime_error { {} }; -struct optional_invalid_dereference: std::runtime_error { - explicit optional_invalid_dereference(const std::string& what_str) - : std::runtime_error(what_str) - {} - - optional_invalid_dereference() - : std::runtime_error("derefernce of optional<void> value") - {} -}; - struct nothing_t {}; constexpr nothing_t nothing{}; @@ -68,21 +58,21 @@ namespace detail { struct optional_tag {}; template <typename X> - using is_optional = std::is_base_of<optional_tag, typename std::decay<X>::type>; + using is_optional = std::is_base_of<optional_tag, decay_t<X>>; - template <typename D,typename X> + template <typename D, typename X> struct wrapped_type_impl { using type = X; }; - template <typename D,typename X> - struct wrapped_type_impl<optional<D>,X> { + template <typename D, typename X> + struct wrapped_type_impl<optional<D>, X> { using type = D; }; template <typename X> struct wrapped_type { - using type = typename wrapped_type_impl<typename std::decay<X>::type,X>::type; + using type = typename wrapped_type_impl<decay_t<X>, X>::type; }; template <typename X> @@ -96,10 +86,10 @@ namespace detail { using data_type = util::uninitialized<X>; public: - using reference_type = typename data_type::reference_type; - using const_reference_type = typename data_type::const_reference_type; - using pointer_type = typename data_type::pointer_type; - using const_pointer_type = typename data_type::const_pointer_type; + using reference = typename data_type::reference; + using const_reference = typename data_type::const_reference; + using pointer = typename data_type::pointer; + using const_pointer = typename data_type::const_pointer; protected: bool set; @@ -114,8 +104,8 @@ namespace detail { } } - reference_type ref() { return data.ref(); } - const_reference_type ref() const { return data.cref(); } + reference ref() { return data.ref(); } + const_reference ref() const { return data.cref(); } public: ~optional_base() { @@ -124,28 +114,24 @@ namespace detail { } } - const_pointer_type operator->() const { return data.ptr(); } - pointer_type operator->() { return data.ptr(); } + pointer operator->() { return data.ptr(); } + const_pointer operator->() const { return data.ptr(); } - const_reference_type operator*() const { return ref(); } - reference_type operator*() { return ref(); } + reference operator*() { return ref(); } + const_reference operator*() const { return ref(); } - reference_type get() { - if (set) { - return ref(); - } - else { + reference get() { + if (!set) { throw optional_unset_error(); } + return ref(); } - const_reference_type get() const { - if (set) { - return ref(); - } - else { + const_reference get() const { + if (!set) { throw optional_unset_error(); } + return ref(); } explicit operator bool() const { return set; } @@ -206,16 +192,16 @@ namespace detail { private: template <typename R, bool F_void_return> struct bind_impl { - template <typename DT,typename F> - static R bind(DT& d,F&& f) { + template <typename DT, typename F> + static R bind(DT& d, F&& f) { return R(d.apply(std::forward<F>(f))); } }; template <typename R> - struct bind_impl<R,true> { - template <typename DT,typename F> - static R bind(DT& d,F&& f) { + struct bind_impl<R, true> { + template <typename DT, typename F> + static R bind(DT& d, F&& f) { d.apply(std::forward<F>(f)); return R(true); } @@ -243,30 +229,27 @@ struct optional: detail::optional_base<X> { using base::reset; using base::data; - optional(): base() {} - optional(nothing_t): base() {} + optional() noexcept: base() {} + optional(nothing_t) noexcept: base() {} - template < - typename Y = X, - typename = enable_if_copy_constructible_t<Y> - > - optional(const X& x): base(true, x) {} + optional(const X& x) + noexcept(std::is_nothrow_copy_constructible<X>::value): base(true, x) {} - template < - typename Y = X, - typename = enable_if_move_constructible_t<Y> - > - optional(X&& x): base(true, std::move(x)) {} + optional(X&& x) + noexcept(std::is_nothrow_move_constructible<X>::value): base(true, std::move(x)) {} optional(const optional& ot): base(ot.set, ot.ref()) {} template <typename T> - optional(const optional<T>& ot): base(ot.set, ot.ref()) {} + optional(const optional<T>& ot) + noexcept(std::is_nothrow_constructible<X, T>::value): base(ot.set, ot.ref()) {} - optional(optional&& ot): base(ot.set, std::move(ot.ref())) {} + optional(optional&& ot) + noexcept(std::is_nothrow_move_constructible<X>::value): base(ot.set, std::move(ot.ref())) {} template <typename T> - optional(optional<T>&& ot): base(ot.set, std::move(ot.ref())) {} + optional(optional<T>&& ot) + noexcept(std::is_nothrow_constructible<X, T&&>::value): base(ot.set, std::move(ot.ref())) {} optional& operator=(nothing_t) { reset(); @@ -338,12 +321,12 @@ struct optional<X&>: detail::optional_base<X&> { using base::data; using base::reset; - optional(): base() {} - optional(nothing_t): base() {} - optional(X&x): base(true,x) {} + optional() noexcept: base() {} + optional(nothing_t) noexcept: base() {} + optional(X& x) noexcept: base(true, x) {} template <typename T> - optional(optional<T&>& ot): base(ot.set,ot.ref()) {} + optional(optional<T&>& ot) noexcept: base(ot.set, ot.ref()) {} optional& operator=(nothing_t) { reset(); @@ -380,10 +363,10 @@ struct optional<void>: detail::optional_base<void> { optional(): base() {} template <typename T> - optional(T): base(true,true) {} + optional(T): base(true, true) {} template <typename T> - optional(const optional<T>& o): base(o.set,true) {} + optional(const optional<T>& o): base(o.set, true) {} optional& operator=(nothing_t) { reset(); @@ -421,7 +404,7 @@ typename std::enable_if< >::type > >::type -operator|(A&& a,B&& b) { +operator|(A&& a, B&& b) { return detail::decay_bool(a) ? a : b; } @@ -430,7 +413,7 @@ typename std::enable_if< detail::is_optional<A>::value || detail::is_optional<B>::value, optional<detail::wrapped_type_t<B>> >::type -operator&(A&& a,B&& b) { +operator&(A&& a, B&& b) { using result_type = optional<detail::wrapped_type_t<B>>; return a ? b: result_type(); } diff --git a/src/util/partition.hpp b/src/util/partition.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b35f81de8fa1f40217e331dd96e09a11eba1c174 --- /dev/null +++ b/src/util/partition.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include <iterator> +#include <stdexcept> +#include <type_traits> + +#include <util/either.hpp> +#include <util/meta.hpp> +#include <util/partition_iterator.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +struct invalid_partition: std::runtime_error { + explicit invalid_partition(const std::string& what): std::runtime_error(what) {} + explicit invalid_partition(const char* what): std::runtime_error(what) {} +}; + +/* + * Present a sequence with monotically increasing values as a partition, + */ +template <typename I> +class partition_range: public range<partition_iterator<I>> { + using base = range<partition_iterator<I>>; + using inner_value_type = typename std::iterator_traits<I>::value_type; + +public: + using typename base::iterator; + using typename base::value_type; + using base::left; + using base::right; + using base::front; + using base::back; + using base::empty; + + template <typename Seq> + partition_range(const Seq& s): base{std::begin(s), upto(std::begin(s), std::end(s))} { + EXPECTS(is_valid()); + } + + // explicitly check invariants + void validate() const { + auto ok = is_valid(); + if (!ok) { + throw invalid_partition(ok.second()); + } + } + + // find half-open sub-interval containing x + iterator find(const inner_value_type& x) const { + if (empty()) { + return right; + } + + auto divs = divisions(); + auto i = std::upper_bound(divs.left, divs.right, x); + if (i==divs.left || i==divs.right) { + return right; + } + return iterator{std::prev(i)}; + } + + // access to underlying divisions + range<I> divisions() const { + return {left.get(), std::next(right.get())}; + } + + // global upper and lower bounds of partition + value_type bounds() const { + return {front().first, back().second}; + } + +private: + either<bool, std::string> is_valid() const { + if (!std::is_sorted(left.get(), right.get())) { + return std::string("offsets are not monotonically increasing"); + } + else { + return true; + } + } +}; + + +template < + typename Seq, + typename SeqIter = typename sequence_traits<Seq>::const_iterator, + typename = enable_if_t<is_forward_iterator<SeqIter>::value> +> +partition_range<SeqIter> partition_view(const Seq& r) { + return partition_range<SeqIter>(r); +} + +/* + * Construct a monotonically increasing sequence in a provided + * container representing a partition from a sequence of subset sizes. + * + * If the first parameter is `partition_in_place`, the provided + * container `divisions` will not be resized, and the partition will + * be of length `util::size(divisions)-1` or zero if `divisions` is + * empty. + * + * Otherwise, `divisions` will be be resized to `util::size(sizes)+1` + * and represent a partition of length `util::size(sizes)`. + * + * Returns a partition view over `divisions`. + */ + +struct partition_in_place_t { + constexpr partition_in_place_t() {} +}; + +constexpr partition_in_place_t partition_in_place; + +template < + typename Part, + typename Sizes, + typename T = typename sequence_traits<Part>::value_type +> +partition_range<typename sequence_traits<Part>::const_iterator> +make_partition(partition_in_place_t, Part& divisions, const Sizes& sizes, T from=T{}) { + auto pi = std::begin(divisions); + auto pe = std::end(divisions); + auto si = std::begin(sizes); + auto se = std::end(sizes); + + if (pi!=pe) { + *pi++ = from; + while (pi!=pe && si!=se) { + from += *si++; + *pi++ = from; + } + while (pi!=pe) { + *pi++ = from; + } + } + return partition_view(divisions); +} + +template < + typename Part, + typename Sizes, + typename T = typename sequence_traits<Part>::value_type +> +partition_range<typename sequence_traits<Part>::const_iterator> +make_partition(Part& divisions, const Sizes& sizes, T from=T{}) { + divisions.resize(size(sizes)+1); + + // (would use std::inclusive_scan in C++17) + auto pi = std::begin(divisions); + for (const auto& s: sizes) { + *pi++ = from; + from += s; + } + *pi = from; + + return partition_view(divisions); +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/partition_iterator.hpp b/src/util/partition_iterator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f13b82f25d43c604680ff4e96b3efd6936e98aea --- /dev/null +++ b/src/util/partition_iterator.hpp @@ -0,0 +1,66 @@ +#pragma once + +/* + * Present a monotonically increasing sequence of values given by an iterator + * as a sequence of pairs representing half-open intervals. + * + * Implementation is a thin wrapper over underlying iterator. + */ + +#include <iterator> +#include <memory> +#include <type_traits> +#include <utility> + +#include <util/iterutil.hpp> +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I> +class partition_iterator: public iterator_adaptor<partition_iterator<I>, I> { + using base = iterator_adaptor<partition_iterator<I>, I>; + friend class iterator_adaptor<partition_iterator<I>, I>; + I inner_; + + // provides access to inner iterator for adaptor. + const I& inner() const { return inner_; } + I& inner() { return inner_; } + + using inner_value_type = decay_t<decltype(*inner_)>; + +public: + using typename base::difference_type; + using value_type = std::pair<inner_value_type, inner_value_type>; + using pointer = const value_type*; + using reference = const value_type&; + + template < + typename J, + typename = enable_if_t<!std::is_same<decay_t<J>, partition_iterator>::value> + > + explicit partition_iterator(J&& c): inner_{std::forward<J>(c)} {} + + // forward and input iterator requirements + + value_type operator*() const { + return {*inner_, *std::next(inner_)}; + } + + util::pointer_proxy<value_type> operator->() const { + return **this; + } + + value_type operator[](difference_type n) const { + return *(*this+n); + } + + // public access to inner iterator + const I& get() const { return inner_; } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/range.hpp b/src/util/range.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7777444d0f65d7feebc03f8f3e988a234462cdf6 --- /dev/null +++ b/src/util/range.hpp @@ -0,0 +1,332 @@ +#pragma once + +/* Present a pair of iterators as a non-owning collection. + * + * Two public member fields, `left` and `right`, describe + * the half-open interval [`left`, `right`). + * + * Constness of the range object only affects mutability + * of the iterators, and does not relate to the constness + * of the data to which the iterators refer. + * + * The `right` field may differ in type from the `left` field, + * in which case it is regarded as a sentinel type; the end of + * the interval is then marked by the first successor `i` of + * `left` that satisfies `i==right`. + * + * For an iterator `i` and sentinel `s`, it is expected that + * the tests `i==s` and `i!=s` are well defined, with the + * corresponding semantics. + */ + +#include <cstddef> +#include <iterator> +#include <limits> +#include <stdexcept> +#include <type_traits> +#include <utility> + +#ifdef WITH_TBB +#include <tbb/tbb_stddef.h> +#endif + +#include <util/counter.hpp> +#include <util/debug.hpp> +#include <util/either.hpp> +#include <util/iterutil.hpp> +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename U, typename S = U> +struct range { + using iterator = U; + using sentinel = S; + using const_iterator = iterator; + using difference_type = typename std::iterator_traits<iterator>::difference_type; + using size_type = typename std::make_unsigned<difference_type>::type; + using value_type = typename std::iterator_traits<iterator>::value_type; + using reference = typename std::iterator_traits<iterator>::reference; + using const_reference = const value_type&; + + iterator left; + sentinel right; + + range() = default; + range(const range&) = default; + range(range&&) = default; + + template <typename U1, typename U2> + range(U1&& l, U2&& r): + left(std::forward<U1>(l)), right(std::forward<U2>(r)) + {} + + range& operator=(const range&) = default; + range& operator=(range&&) = default; + + bool empty() const { return left==right; } + + iterator begin() const { return left; } + const_iterator cbegin() const { return left; } + + sentinel end() const { return right; } + sentinel cend() const { return right; } + + template <typename V = iterator> + enable_if_t<is_forward_iterator<V>::value, size_type> + size() const { + return std::distance(begin(), end()); + } + + constexpr size_type max_size() const { return std::numeric_limits<size_type>::max(); } + + void swap(range& other) { + std::swap(left, other.left); + std::swap(right, other.right); + } + + decltype(*left) front() const { return *left; } + + decltype(*left) back() const { return *upto(left, right); } + + template <typename V = iterator> + enable_if_t<is_random_access_iterator<V>::value, decltype(*left)> + operator[](difference_type n) const { + return *std::next(begin(), n); + } + + template <typename V = iterator> + enable_if_t<is_random_access_iterator<V>::value, decltype(*left)> + at(difference_type n) const { + if (size_type(n) >= size()) { + throw std::out_of_range("out of range in range"); + } + return (*this)[n]; + } + +#ifdef WITH_TBB + template < + typename V = iterator, + typename = enable_if_t<is_forward_iterator<V>::value> + > + range(range& r, tbb::split): + left(r.left), right(r.right) + { + std::advance(left, r.size()/2u); + r.right = left; + } + + template < + typename V = iterator, + typename = enable_if_t<is_forward_iterator<V>::value> + > + range(range& r, tbb::proportional_split p): + left(r.left), right(r.right) + { + size_type i = (r.size()*p.left())/(p.left()+p.right()); + if (i<1) { + i = 1; + } + std::advance(left, i); + r.right = left; + } + + bool is_divisible() const { + return is_forward_iterator<U>::value && left != right && std::next(left) != right; + } + + static const bool is_splittable_in_proportion() { + return is_forward_iterator<U>::value; + } +#endif +}; + +template <typename U, typename V> +range<U, V> make_range(const U& left, const V& right) { + return range<U, V>(left, right); +} + +/* + * Use a proxy iterator to present a range as having the same begin and + * end types, for use with e.g. pre-C++17 ranged-for loops or STL + * algorithms. + */ +template <typename I, typename S> +class sentinel_iterator { + nest::mc::util::either<I, S> e_; + + bool is_sentinel() const { return e_.index()!=0; } + + I& iter() { + EXPECTS(!is_sentinel()); + return e_.template unsafe_get<0>(); + } + + const I& iter() const { + EXPECTS(!is_sentinel()); + return e_.template unsafe_get<0>(); + } + + S& sentinel() { + EXPECTS(is_sentinel()); + return e_.template unsafe_get<1>(); + } + + const S& sentinel() const { + EXPECTS(is_sentinel()); + return e_.template unsafe_get<1>(); + } + +public: + using difference_type = typename std::iterator_traits<I>::difference_type; + using value_type = typename std::iterator_traits<I>::value_type; + using pointer = typename std::iterator_traits<I>::pointer; + using reference = typename std::iterator_traits<I>::reference; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + + sentinel_iterator(I i): e_(i) {} + + template <typename V = S, typename = enable_if_t<!std::is_same<I, V>::value>> + sentinel_iterator(S i): e_(i) {} + + sentinel_iterator() = default; + sentinel_iterator(const sentinel_iterator&) = default; + sentinel_iterator(sentinel_iterator&&) = default; + + sentinel_iterator& operator=(const sentinel_iterator&) = default; + sentinel_iterator& operator=(sentinel_iterator&&) = default; + + // forward and input iterator requirements + + auto operator*() const -> decltype(*iter()) { return *iter(); } + + I operator->() const { return e_.template ptr<0>(); } + + sentinel_iterator& operator++() { + ++iter(); + return *this; + } + + sentinel_iterator operator++(int) { + sentinel_iterator c(*this); + ++*this; + return c; + } + + bool operator==(const sentinel_iterator& x) const { + if (is_sentinel()) { + return x.is_sentinel() || x.iter()==sentinel(); + } + else { + return x.is_sentinel()? iter()==x.sentinel(): iter()==x.iter(); + } + } + + bool operator!=(const sentinel_iterator& x) const { + return !(*this==x); + } + + // bidirectional iterator requirements + + sentinel_iterator& operator--() { + --iter(); + return *this; + } + + sentinel_iterator operator--(int) { + sentinel_iterator c(*this); + --*this; + return c; + } + + // random access iterator requirements + + sentinel_iterator &operator+=(difference_type n) { + iter() += n; + return *this; + } + + sentinel_iterator operator+(difference_type n) const { + sentinel_iterator c(*this); + return c += n; + } + + friend sentinel_iterator operator+(difference_type n, sentinel_iterator x) { + return x+n; + } + + sentinel_iterator& operator-=(difference_type n) { + iter() -= n; + return *this; + } + + sentinel_iterator operator-(difference_type n) const { + sentinel_iterator c(*this); + return c -= n; + } + + difference_type operator-(sentinel_iterator x) const { + return iter()-x.iter(); + } + + auto operator[](difference_type n) const -> decltype(*iter()){ + return *(iter()+n); + } + + bool operator<=(const sentinel_iterator& x) const { + return x.is_sentinel() || (!is_sentinel() && iter()<=x.iter()); + } + + bool operator<(const sentinel_iterator& x) const { + return !is_sentinel() && (x.is_sentinel() || iter()<=x.iter()); + } + + bool operator>=(const sentinel_iterator& x) const { + return !(x<*this); + } + + bool operator>(const sentinel_iterator& x) const { + return !(x<=*this); + } +}; + +template <typename I, typename S> +using sentinel_iterator_t = + typename std::conditional<std::is_same<I, S>::value, I, sentinel_iterator<I, S>>::type; + +template <typename I, typename S> +sentinel_iterator_t<I, S> make_sentinel_iterator(const I& i, const S& s) { + return sentinel_iterator_t<I, S>(i); +} + +template <typename I, typename S> +sentinel_iterator_t<I, S> make_sentinel_end(const I& i, const S& s) { + return sentinel_iterator_t<I, S>(s); +} + +template <typename Seq> +auto canonical_view(const Seq& s) -> + range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>> +{ + return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))}; +} + +/* + * Present a single item as a range + */ + +template <typename T> +range<T*> singleton_view(T& item) { + return {&item, &item+1}; +} + +template <typename T> +range<const T*> singleton_view(const T& item) { + return {&item, &item+1}; +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/span.hpp b/src/util/span.hpp new file mode 100644 index 0000000000000000000000000000000000000000..06cdfa36924324c589af8a16412d8c167fb0429f --- /dev/null +++ b/src/util/span.hpp @@ -0,0 +1,33 @@ +#pragma once + +/* + * Presents a half-open interval [a,b) of integral values as a container. + */ + +#include <type_traits> +#include <utility> + +#include <util/counter.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I> +using span = range<counter<I>>; + +template <typename I, typename J> +span<typename std::common_type<I, J>::type> make_span(I left, J right) { + return span<typename std::common_type<I, J>::type>(left, right); +} + +template <typename I, typename J> +span<typename std::common_type<I, J>::type> make_span(std::pair<I, J> interval) { + return span<typename std::common_type<I, J>::type>(interval.first, interval.second); +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/transform.hpp b/src/util/transform.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f7d9fed04c721f09611757c84c54e5f13cd15942 --- /dev/null +++ b/src/util/transform.hpp @@ -0,0 +1,109 @@ +#pragma once + +/* + * An iterator adaptor that presents the values from an underlying + * iterator after applying a provided functor. + */ + +#include <iterator> +#include <memory> +#include <type_traits> + +#include <util/iterutil.hpp> +#include <util/meta.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I, typename F> +class transform_iterator: public iterator_adaptor<transform_iterator<I, F>, I> { + using base = iterator_adaptor<transform_iterator<I, F>, I>; + friend class iterator_adaptor<transform_iterator<I, F>, I>; + + I inner_; + F f_; + + // provides access to inner iterator for adaptor. + const I& inner() const { return inner_; } + I& inner() { return inner_; } + + using inner_value_type = util::decay_t<decltype(*inner_)>; + +public: + using typename base::difference_type; + using value_type = decltype(f_(*inner_)); + using pointer = const value_type*; + using reference = const value_type&; + + template <typename J, typename G> + transform_iterator(J&& c, G&& g): inner_{std::forward<J>(c)}, f_{std::forward<G>(g)} {} + + transform_iterator(const transform_iterator&) = default; + transform_iterator(transform_iterator&&) = default; + transform_iterator& operator=(const transform_iterator&) = default; + transform_iterator& operator=(transform_iterator&&) = default; + + // forward and input iterator requirements + + value_type operator*() const { + return f_(*inner_); + } + + util::pointer_proxy<value_type> operator->() const { + return **this; + } + + value_type operator[](difference_type n) const { + return *(*this+n); + } + + // public access to inner iterator + const I& get() const { return inner_; } + + bool operator==(const transform_iterator& x) const { return inner_==x.inner_; } + bool operator!=(const transform_iterator& x) const { return inner_!=x.inner_; } + + // expose inner iterator for testing against a sentinel + template <typename Sentinel> + bool operator==(const Sentinel& s) const { return inner_==s; } + + template <typename Sentinel> + bool operator!=(const Sentinel& s) const { return !(inner_==s); } +}; + +template <typename I, typename F> +transform_iterator<I, util::decay_t<F>> make_transform_iterator(const I& i, const F& f) { + return transform_iterator<I, util::decay_t<F>>(i, f); +} + +template < + typename Seq, + typename F, + typename seq_citer = typename sequence_traits<Seq>::const_iterator, + typename seq_csent = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<std::is_same<seq_citer, seq_csent>::value> +> +range<transform_iterator<seq_citer, util::decay_t<F>>> +transform_view(const Seq& s, const F& f) { + return {make_transform_iterator(cbegin(s), f), make_transform_iterator(cend(s), f)}; +} + + +template < + typename Seq, + typename F, + typename seq_citer = typename sequence_traits<Seq>::const_iterator, + typename seq_csent = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<!std::is_same<seq_citer, seq_csent>::value> +> +range<transform_iterator<seq_citer, util::decay_t<F>>, seq_csent> +transform_view(const Seq& s, const F& f) { + return {make_transform_iterator(cbegin(s), f), cend(s)}; +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/uninitialized.hpp b/src/util/uninitialized.hpp index 846e46d5a2451fe1535f026ee4b801e5303a6f0c..8e3613cd6b342b40c7f2cd76edfd4e7acec5c582 100644 --- a/src/util/uninitialized.hpp +++ b/src/util/uninitialized.hpp @@ -18,25 +18,26 @@ namespace nest { namespace mc { namespace util { -/* Maintains storage for a value of type X, with explicit +/* + * Maintains storage for a value of type X, with explicit * construction and destruction. */ template <typename X> -struct uninitialized { +class uninitialized { private: typename std::aligned_storage<sizeof(X), alignof(X)>::type data; public: - using pointer_type = X*; - using const_pointer_type = const X*; - using reference_type = X&; - using const_reference_type = const X&; + using pointer = X*; + using const_pointer = const X*; + using reference = X&; + using const_reference= const X&; - pointer_type ptr() { return reinterpret_cast<X*>(&data); } - const_pointer_type cptr() const { return reinterpret_cast<const X*>(&data); } + pointer ptr() { return reinterpret_cast<X*>(&data); } + const_pointer cptr() const { return reinterpret_cast<const X*>(&data); } - reference_type ref() { return *reinterpret_cast<X*>(&data); } - const_reference_type cref() const { return *reinterpret_cast<const X*>(&data); } + reference ref() { return *reinterpret_cast<X*>(&data); } + const_reference cref() const { return *reinterpret_cast<const X*>(&data); } // Copy construct the value. template < @@ -60,45 +61,46 @@ public: // Apply the one-parameter functor F to the value by reference. template <typename F> - result_of_t<F(reference_type)> apply(F&& f) { return f(ref()); } + result_of_t<F(reference)> apply(F&& f) { return f(ref()); } // Apply the one-parameter functor F to the value by const reference. template <typename F> - result_of_t<F(const_reference_type)> apply(F&& f) const { return f(cref()); } + result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); } }; -/* Maintains storage for a pointer of type X, representing +/* + * Maintains storage for a pointer of type X, representing * a possibly uninitialized reference. */ template <typename X> -struct uninitialized<X&> { +class uninitialized<X&> { private: X *data; public: - using pointer_type = X*; - using const_pointer_type = const X*; - using reference_type = X&; - using const_reference_type = const X&; + using pointer = X*; + using const_pointer = const X*; + using reference = X&; + using const_reference = const X&; - pointer_type ptr() { return data; } - const_pointer_type cptr() const { return data; } + pointer ptr() { return data; } + const_pointer cptr() const { return data; } - reference_type ref() { return *data; } - const_reference_type cref() const { return *data; } + reference ref() { return *data; } + const_reference cref() const { return *data; } void construct(X& x) { data = &x; } void destruct() {} // Apply the one-parameter functor F to the value by reference. template <typename F> - result_of_t<F(reference_type)> apply(F&& f) { + result_of_t<F(reference)> apply(F&& f) { return f(ref()); } // Apply the one-parameter functor F to the value by const reference. template <typename F> - result_of_t<F(const_reference_type)> apply(F&& f) const { + result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); } }; @@ -108,17 +110,18 @@ public: * Allows the use of uninitialized<X> for void X, for generic applications. */ template <> -struct uninitialized<void> { - using pointer_type = void*; - using const_pointer_type = const void*; - using reference_type = void; - using const_reference_type = void; +class uninitialized<void> { +public: + using pointer = void*; + using const_pointer = const void*; + using reference = void; + using const_reference = void; - pointer_type ptr() { return nullptr; } - const_pointer_type cptr() const { return nullptr; } + pointer ptr() { return nullptr; } + const_pointer cptr() const { return nullptr; } - reference_type ref() {} - const_reference_type cref() const {} + reference ref() {} + const_reference cref() const {} // No operation. void construct(...) {} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 83c537b26385b62ece833d59931ece57bc1cd8cc..6b34d70125bb4d9792f8abb6b68daf411dfe78d8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -14,6 +14,8 @@ set(TEST_SOURCES test_double_buffer.cpp test_cell.cpp test_compartments.cpp + test_counter.cpp + test_either.cpp test_event_queue.cpp test_fvm.cpp test_cell_group.cpp @@ -24,15 +26,19 @@ set(TEST_SOURCES test_nop.cpp test_optional.cpp test_parameters.cpp + test_partition.cpp test_point.cpp test_probe.cpp test_segment.cpp + test_range.cpp + test_span.cpp test_spikes.cpp test_spike_store.cpp test_stimulus.cpp test_swcio.cpp test_synapses.cpp test_tree.cpp + test_transform.cpp test_uninitialized.cpp # unit test driver diff --git a/tests/unit/test_counter.cpp b/tests/unit/test_counter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c97f2277e08a865ebb0be0a7e7b9d4db18f7360 --- /dev/null +++ b/tests/unit/test_counter.cpp @@ -0,0 +1,131 @@ +#include "gtest.h" + +#include <iterator> +#include <type_traits> + +#include <util/counter.hpp> + +using namespace nest::mc; + +template <typename V> +class counter_test: public ::testing::Test {}; + +TYPED_TEST_CASE_P(counter_test); + +TYPED_TEST_P(counter_test, value) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c0; + EXPECT_EQ(int_type{0}, *c0); + + counter c1{int_type{1}}; + counter c2{int_type{2}}; + + EXPECT_EQ(int_type{1}, *c1); + EXPECT_EQ(int_type{2}, *c2); + + c2 = c1; + EXPECT_EQ(int_type{1}, *c2); + + c2 = int_type{2}; + EXPECT_EQ(int_type{2}, *c2); +} + +TYPED_TEST_P(counter_test, compare) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{2}}; + + EXPECT_LT(c1, c2); + EXPECT_LE(c1, c2); + EXPECT_NE(c1, c2); + EXPECT_GE(c2, c1); + EXPECT_GT(c2, c1); + + counter c1bis{int_type{1}}; + + EXPECT_LE(c1, c1bis); + EXPECT_EQ(c1, c1bis); + EXPECT_GE(c1, c1bis); +} + +TYPED_TEST_P(counter_test, arithmetic) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{10}}; + int_type nine{9}; + + EXPECT_EQ(nine, c2-c1); + EXPECT_EQ(c2, c1+nine); + EXPECT_EQ(c2, nine+c1); + EXPECT_EQ(c1, c2-nine); + + counter c3 = c1; + counter c4 = (c3 += nine); + + EXPECT_EQ(c2, c3); + EXPECT_EQ(c3, c4); + + c3 = c2; + c4 = (c3 -= nine); + + EXPECT_EQ(c1, c3); + EXPECT_EQ(c3, c4); + + c3 = c1; + EXPECT_EQ(counter{2}, ++c3); + EXPECT_EQ(counter{3}, ++c3); + EXPECT_EQ(counter{2}, --c3); + EXPECT_EQ(counter{1}, --c3); + + c3 = c1; + EXPECT_EQ(counter{1}, c3++); + EXPECT_EQ(counter{2}, c3++); + EXPECT_EQ(counter{3}, c3--); + EXPECT_EQ(counter{2}, c3--); + + EXPECT_EQ(int_type{10}, c2[0]); + EXPECT_EQ(int_type{4}, c2[-6]); + EXPECT_EQ(int_type{19}, c2[9]); +} + +TYPED_TEST_P(counter_test, iterator_traits) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + using traits = std::iterator_traits<counter>; + + typename traits::reference r = *counter{int_type{3}}; + EXPECT_EQ(r, int_type{3}); + + typename traits::difference_type d = counter{int_type{4}} - counter{int_type{7}}; + EXPECT_EQ(typename traits::difference_type(-3), d); + + EXPECT_TRUE((std::is_same<std::random_access_iterator_tag, typename traits::iterator_category>::value)); +} + +TYPED_TEST_P(counter_test, iterator_functions) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{10}}; + + EXPECT_EQ(int_type{9}, std::distance(c1,c2)); + counter c3{c1}; + std::advance(c3, int_type{9}); + EXPECT_EQ(c2, c3); + + EXPECT_EQ(counter{int_type{2}}, std::next(c1)); + EXPECT_EQ(counter{int_type{9}}, std::prev(c2)); +} + +REGISTER_TYPED_TEST_CASE_P(counter_test, value, compare, arithmetic, iterator_traits, iterator_functions); + +using int_types = ::testing::Types<signed char, unsigned char, short, unsigned short, int, unsigned, std::size_t, std::ptrdiff_t>; +INSTANTIATE_TYPED_TEST_CASE_P(int_types, counter_test, int_types); + diff --git a/tests/unit/test_either.cpp b/tests/unit/test_either.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d185d8bf5eea7c7f5d0e75395d6f7e6d0f46f53 --- /dev/null +++ b/tests/unit/test_either.cpp @@ -0,0 +1,64 @@ +#include <typeinfo> +#include <array> +#include <algorithm> + +#include "gtest.h" +#include "util/either.hpp" + +// TODO: coverage! + +using namespace nest::mc::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/tests/unit/test_partition.cpp b/tests/unit/test_partition.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edfb95d1021ea452c1701d9a84ef2341ed17e48f --- /dev/null +++ b/tests/unit/test_partition.cpp @@ -0,0 +1,150 @@ +#include "gtest.h" + +#include <array> +#include <forward_list> +#include <string> +#include <vector> + +#include <util/debug.hpp> +#include <util/nop.hpp> +#include <util/partition.hpp> + +using namespace nest::mc; + +TEST(partition, partition_view) { + std::forward_list<int> fl = {1, 4, 6, 8, 10 }; + + auto p1 = util::partition_view(fl); + EXPECT_EQ(std::make_pair(1,4), p1.front()); + EXPECT_EQ(std::make_pair(8,10), p1.back()); + EXPECT_EQ(std::make_pair(1,10), p1.bounds()); + EXPECT_EQ(4u, p1.size()); + + std::vector<double> v = {2.0, 3.6, 7.5}; + + auto p2 = util::partition_view(v); + EXPECT_EQ(2u, p2.size()); + + std::vector<double> ends; + std::vector<double> ends_expected = { 2.0, 3.6, 3.6, 7.5 }; + for (auto b: p2) { + ends.push_back(b.first); + ends.push_back(b.second); + } + EXPECT_EQ(ends_expected, ends); +} + +TEST(partition, short_partition_view) { + int two_divs[] = {10, 15}; + EXPECT_EQ(1u, util::partition_view(two_divs).size()); + + int one_div[] = {10}; + EXPECT_EQ(0u, util::partition_view(one_div).size()); + + std::array<int, 0> zero_divs; + EXPECT_EQ(0u, util::partition_view(zero_divs).size()); +} + +TEST(partition, check_monotonicity) { + // override any EXPECTS checks in partition + util::global_failed_assertion_handler = util::ignore_failed_assertion; + + int divs_ok[] = {1, 2, 2, 3, 3}; + EXPECT_NO_THROW(util::partition_view(divs_ok).validate()); + + int divs_bad[] = {3, 2, 1}; + EXPECT_THROW(util::partition_view(divs_bad).validate(), util::invalid_partition); +} + +TEST(partition, partition_view_find) { + std::vector<double> divs = { 1, 2.5, 3, 5.5 }; + double eps = 0.1; + auto p = util::partition_view(divs); + + EXPECT_EQ(p.end(), p.find(divs.front()-eps)); + EXPECT_NE(p.end(), p.find(divs.front())); + EXPECT_EQ(divs.front(), p.find(divs.front())->first); + + EXPECT_NE(p.end(), p.find(divs.back()-eps)); + EXPECT_EQ(divs.back(), p.find(divs.back()-eps)->second); + EXPECT_EQ(p.end(), p.find(divs.back())); + EXPECT_EQ(p.end(), p.find(divs.back()+eps)); + + EXPECT_EQ(divs[1], p.find(divs[1]+eps)->first); + EXPECT_EQ(divs[2], p.find(divs[1]+eps)->second); +} + +TEST(partition, partition_view_non_numeric) { + std::string divs[] = { "a", "dictionary", "of", "sorted", "words" }; + auto p = util::partition_view(divs); + + EXPECT_EQ("dictionary", p.find("elephant")->first); +} + +TEST(partition, make_partition_in_place) { + unsigned sizes[] = { 7, 3, 0, 2 }; + unsigned part_store[util::size(sizes)+1]; + + auto p = util::make_partition(util::partition_in_place, part_store, sizes, 10u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(10u, 17u), p[0]); + EXPECT_EQ(std::make_pair(17u, 20u), p[1]); + EXPECT_EQ(std::make_pair(20u, 20u), p[2]); + EXPECT_EQ(std::make_pair(20u, 22u), p[3]); + + // with short sizes sequence + unsigned short_sizes[] = { 1, 2 }; + p = util::make_partition(util::partition_in_place, part_store, short_sizes, 0u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(0u, 1u), p[0]); + EXPECT_EQ(std::make_pair(1u, 3u), p[1]); + EXPECT_EQ(std::make_pair(3u, 3u), p[2]); + EXPECT_EQ(std::make_pair(3u, 3u), p[3]); + + // with longer sizes sequence + unsigned long_sizes[] = {1, 2, 3, 4, 5, 6}; + p = util::make_partition(util::partition_in_place, part_store, long_sizes, 0u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(0u, 1u), p[0]); + EXPECT_EQ(std::make_pair(1u, 3u), p[1]); + EXPECT_EQ(std::make_pair(3u, 6u), p[2]); + EXPECT_EQ(std::make_pair(6u, 10u), p[3]); + + // with empty sizes sequence + std::array<unsigned, 0> no_sizes; + p = util::make_partition(util::partition_in_place, part_store, no_sizes, 17u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(17u, 17u), p[0]); + EXPECT_EQ(std::make_pair(17u, 17u), p[1]); + EXPECT_EQ(std::make_pair(17u, 17u), p[2]); + EXPECT_EQ(std::make_pair(17u, 17u), p[3]); + + // with short partition containers + unsigned part_store_one[1]; + p = util::make_partition(util::partition_in_place, part_store_one, sizes, 10u); + ASSERT_EQ(0u, p.size()); + ASSERT_TRUE(p.empty()); + + std::array<unsigned,0> part_store_zero; + p = util::make_partition(util::partition_in_place, part_store_zero, sizes, 10u); + ASSERT_EQ(0u, p.size()); + ASSERT_TRUE(p.empty()); +} + +TEST(partition, make_partition) { + // (also tests differing types for sizes and divisiosn) + unsigned sizes[] = { 7, 3, 0, 2 }; + std::forward_list<double> part_store = { 100.3 }; + + auto p = util::make_partition(part_store, sizes, 10.0); + ASSERT_EQ(4u, p.size()); + + auto pi = p.begin(); + EXPECT_EQ(10.0, pi++->first); + EXPECT_EQ(17.0, pi++->first); + EXPECT_EQ(20.0, pi++->first); + EXPECT_EQ(20.0, pi->first); + EXPECT_EQ(22.0, pi->second); + + EXPECT_EQ(p.end(), ++pi); +} diff --git a/tests/unit/test_range.cpp b/tests/unit/test_range.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7847212e3f0b0546c1f974dbdebab3303a15ea1d --- /dev/null +++ b/tests/unit/test_range.cpp @@ -0,0 +1,171 @@ +#include "gtest.h" + +#include <algorithm> +#include <iterator> +#include <sstream> +#include <list> +#include <numeric> +#include <type_traits> + +#ifdef WITH_TBB +#include <tbb/tbb_stddef.h> +#endif + +#include <util/range.hpp> + +using namespace nest::mc; + +TEST(range, list_iterator) { + std::list<int> l = { 2, 4, 6, 8, 10 }; + + auto s = util::make_range(l.begin(), l.end()); + + EXPECT_EQ(s.left, l.begin()); + EXPECT_EQ(s.right, l.end()); + + EXPECT_EQ(s.begin(), l.begin()); + EXPECT_EQ(s.end(), l.end()); + + EXPECT_EQ(s.size(), l.size()); + EXPECT_EQ(s.front(), *l.begin()); + EXPECT_EQ(s.back(), *std::prev(l.end())); + + int check = std::accumulate(l.begin(), l.end(), 0); + int sum = 0; + for (auto i: s) { + sum += i; + } + + EXPECT_EQ(check, sum); + + auto sum2 = std::accumulate(s.begin(), s.end(), 0); + EXPECT_EQ(check, sum2); +} + +TEST(range, pointer) { + int xs[] = { 10, 11, 12, 13, 14, 15, 16 }; + int l = 2; + int r = 5; + + util::range<int *> s(&xs[l], &xs[r]); + auto s_deduced = util::make_range(xs+l, xs+r); + + EXPECT_TRUE((std::is_same<decltype(s), decltype(s_deduced)>::value)); + EXPECT_EQ(s.left, s_deduced.left); + EXPECT_EQ(s.right, s_deduced.right); + + EXPECT_EQ(3u, s.size()); + + EXPECT_EQ(xs[l], *s.left); + EXPECT_EQ(xs[l], *s.begin()); + EXPECT_EQ(xs[l], s[0]); + EXPECT_EQ(xs[l], s.front()); + + EXPECT_EQ(xs[r], *s.right); + EXPECT_EQ(xs[r], *s.end()); + EXPECT_THROW(s.at(r-l), std::out_of_range); + + EXPECT_EQ(r-l, std::distance(s.begin(), s.end())); + + EXPECT_TRUE(std::equal(s.begin(), s.end(), &xs[l])); +} + +TEST(range, input_iterator) { + int nums[] = { 10, 9, 8, 7, 6 }; + std::istringstream sin("10 9 8 7 6"); + auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>()); + + EXPECT_TRUE(std::equal(s.begin(), s.end(), &nums[0])); +} + +TEST(range, const_iterator) { + std::vector<int> xs = { 1, 2, 3, 4, 5 }; + auto r = util::make_range(xs.begin(), xs.end()); + EXPECT_TRUE((std::is_same<int&, decltype(r.front())>::value)); + + const auto& xs_const = xs; + auto r_const = util::make_range(xs_const.begin(), xs_const.end()); + EXPECT_TRUE((std::is_same<const int&, decltype(r_const.front())>::value)); +} + +struct null_terminated_t { + bool operator==(const char *p) const { return !*p; } + bool operator!=(const char *p) const { return !!*p; } + + friend bool operator==(const char *p, null_terminated_t x) { + return x==p; + } + + friend bool operator!=(const char *p, null_terminated_t x) { + return x!=p; + } + + constexpr null_terminated_t() {} +}; + +constexpr null_terminated_t null_terminated; + +TEST(range, sentinel) { + const char *cstr = "hello world"; + std::string s; + + auto cstr_range = util::make_range(cstr, null_terminated); + for (auto i=cstr_range.begin(); i!=cstr_range.end(); ++i) { + s += *i; + } + + EXPECT_EQ(s, std::string(cstr)); + + s.clear(); + for (auto c: canonical_view(cstr_range)) { + s += c; + } + + EXPECT_EQ(s, std::string(cstr)); +} + +#ifdef WITH_TBB + +TEST(range, tbb_split) { + constexpr std::size_t N = 20; + int xs[N]; + + for (unsigned i = 0; i<N; ++i) { + xs[i] = i; + } + + auto s = util::make_range(&xs[0], &xs[0]+N); + + while (s.size()>1) { + auto ssize = s.size(); + auto r = decltype(s){s, tbb::split{}}; + EXPECT_GT(r.size(), 0u); + EXPECT_GT(s.size(), 0u); + EXPECT_EQ(ssize, r.size()+s.size()); + EXPECT_EQ(s.end(), r.begin()); + + EXPECT_TRUE(r.size()>1 || !r.is_divisible()); + EXPECT_TRUE(s.size()>1 || !s.is_divisible()); + } + + for (unsigned i = 1; i<N-1; ++i) { + s = util::make_range(&xs[0], &xs[0]+N); + // expect exact splitting by proportion in this instance + + auto r = decltype(s){s, tbb::proportional_split{i, N-i}}; + EXPECT_EQ(&xs[0], s.left); + EXPECT_EQ(&xs[0]+i, s.right); + EXPECT_EQ(&xs[0]+i, r.left); + EXPECT_EQ(&xs[0]+N, r.right); + } +} + +TEST(range, tbb_no_split) { + std::istringstream sin("10 9 8 7 6"); + auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>()); + + EXPECT_FALSE(decltype(s)::is_splittable_in_proportion()); + EXPECT_FALSE(s.is_divisible()); +} + +#endif diff --git a/tests/unit/test_span.cpp b/tests/unit/test_span.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edb55f7d65e7f0321add82de6e0879143050fede --- /dev/null +++ b/tests/unit/test_span.cpp @@ -0,0 +1,93 @@ +#include "gtest.h" + +#include <algorithm> +#include <iterator> +#include <list> +#include <numeric> +#include <type_traits> +#include <utility> + +#include <util/span.hpp> + +using namespace nest::mc; + +TEST(span, int_access) { + using span = util::span<int>; + + int n = 97; + int a = 3; + int b = a+n; + + span s(a, b); + EXPECT_EQ(s.left, a); + EXPECT_EQ(s.right, b); + + EXPECT_EQ(s.size(), std::size_t(n)); + + EXPECT_EQ(s.front(), a); + EXPECT_EQ(s.back(), b-1); + + EXPECT_EQ(s[0], a); + EXPECT_EQ(s[1], a+1); + EXPECT_EQ(s[n-1], b-1); + + EXPECT_NO_THROW(s.at(0)); + EXPECT_NO_THROW(s.at(n-1)); + EXPECT_THROW(s.at(n), std::out_of_range); + EXPECT_THROW(s.at(n+1), std::out_of_range); + EXPECT_THROW(s.at(-1), std::out_of_range); +} + +TEST(span, int_iterators) { + using span = util::span<int>; + + int n = 97; + int a = 3; + int b = a+n; + + span s(a, b); + + EXPECT_TRUE(util::is_iterator<span::iterator>::value); + EXPECT_TRUE(util::is_random_access_iterator<span::iterator>::value); + + EXPECT_EQ(n, std::distance(s.begin(), s.end())); + EXPECT_EQ(n, std::distance(s.cbegin(), s.cend())); + + int sum = 0; + for (auto i: span(a, b)) { + sum += i; + } + EXPECT_EQ(sum, (a+b-1)*(b-a)/2); +} + +TEST(span, make_span) { + auto s_empty = util::make_span(3, 3); + EXPECT_TRUE(s_empty.empty()); + + { + auto s = util::make_span((short)3, (unsigned long long)10); + auto first = s.front(); + auto last = s.back(); + + EXPECT_EQ(3u, first); + EXPECT_EQ(9u, last); + + EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value)); + EXPECT_TRUE((std::is_same<unsigned long long, decltype(first)>::value)); + } + + { + // type abuse! should promote bool to long in span. + std::pair<long, bool> bounds(-3, false); + auto s = util::make_span(bounds); + auto first = s.front(); + auto last = s.back(); + + EXPECT_EQ(-3, first); + EXPECT_EQ(-1, last); + + EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value)); + EXPECT_TRUE((std::is_same<long, decltype(first)>::value)); + } +} + diff --git a/tests/unit/test_transform.cpp b/tests/unit/test_transform.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9212293fbf7a59674eb81f3ccac1638b1df04077 --- /dev/null +++ b/tests/unit/test_transform.cpp @@ -0,0 +1,56 @@ +#include "gtest.h" + +#include <cctype> +#include <forward_list> +#include <vector> + +#include <util/range.hpp> +#include <util/transform.hpp> + +using namespace nest::mc; + +TEST(transform, transform_view) { + std::forward_list<int> fl = {1, 4, 6, 8, 10 }; + std::vector<double> result; + + auto r = util::transform_view(fl, [](int i) { return i*i+0.5; }); + + EXPECT_EQ(5u, util::size(r)); + EXPECT_EQ(16.5, *(std::next(std::begin(r), 1))); + + std::copy(r.begin(), r.end(), std::back_inserter(result)); + std::vector<double> expected = { 1.5, 16.5, 36.5, 64.5, 100.5 }; + + EXPECT_EQ(expected, result); +} + +struct null_terminated_t { + bool operator==(const char *p) const { return !*p; } + bool operator!=(const char *p) const { return !!*p; } + + friend bool operator==(const char *p, null_terminated_t x) { + return x==p; + } + + friend bool operator!=(const char *p, null_terminated_t x) { + return x!=p; + } + + constexpr null_terminated_t() {} +}; + +constexpr null_terminated_t null_terminated; + +char upper(char c) { return std::toupper(c); } + +TEST(transform, transform_view_sentinel) { + const char *hello = "hello"; + auto r = util::transform_view(util::make_range(hello, null_terminated), upper); + + std::string out; + for (auto i = r.begin(); i!=r.end(); ++i) { + out += *i; + } + EXPECT_EQ("HELLO", out); +} +