diff --git a/src/util/iterutil.hpp b/src/util/iterutil.hpp index 6a9fd159ffe9f75eddf57f2adb5c4d42cb9e7861..a357b667b488a8796520e70a6e84901f61e65033 100644 --- a/src/util/iterutil.hpp +++ b/src/util/iterutil.hpp @@ -43,6 +43,30 @@ upto(I iter, E end) { return iter==I{end}? iter: I{std::prev(end)}; } +template <typename I, typename E, + typename C = typename common_random_access_iterator<I,E>::type> +enable_if_t<std::is_same<I, E>::value || + (has_common_random_access_iterator<I,E>::value && + is_forward_iterator<I>::value), + typename std::iterator_traits<C>::difference_type> +distance(I first, E last) { + return std::distance(static_cast<C>(first), static_cast<C>(last)); +} + +template <typename I, typename E> +enable_if_t<!has_common_random_access_iterator<I, E>::value && + is_forward_iterator<I>::value, + typename std::iterator_traits<I>::difference_type> +distance(I first, E last) { + typename std::iterator_traits<I>::difference_type ret = 0; + while (first != last) { + ++first; + ++ret; + } + + return ret; +} + /* * Provide a proxy object for operator->() for iterator adaptors that * present rvalues on dereference. diff --git a/src/util/meta.hpp b/src/util/meta.hpp index 40f08d481f23199e8e06a47fc41acdd58fdab8c9..e45d31604d08b53dea1a722cd329c241ffa4da36 100644 --- a/src/util/meta.hpp +++ b/src/util/meta.hpp @@ -151,6 +151,36 @@ struct is_forward_iterator<T, enable_if_t< template <typename T> using is_forward_iterator_t = typename is_forward_iterator<T>::type; + +template <typename I, typename E, typename = void, typename = void> +struct common_random_access_iterator {}; + +template <typename I, typename E> +struct common_random_access_iterator< + I, + E, + void_t<decltype(false ? std::declval<I>() : std::declval<E>())>, + enable_if_t< + is_random_access_iterator< + decay_t<decltype(false ? std::declval<I>() : std::declval<E>())> + >::value + > +> { + using type = decay_t< + decltype(false ? std::declval<I>() : std::declval<E>()) + >; +}; + +template <typename I, typename E, typename = void> +struct has_common_random_access_iterator: public std::false_type {}; + +template <typename I, typename E> +struct has_common_random_access_iterator< + I, E, void_t<typename common_random_access_iterator<I, E>::type> +> : public std::true_type {}; + + + } // namespace util } // namespace mc } // namespace nest diff --git a/src/util/range.hpp b/src/util/range.hpp index 2439ca74ee785fac1f909e92b3189bbfcd6f5da2..9e3bd62a620ca40ddad3b3d178be95c4738faef1 100644 --- a/src/util/range.hpp +++ b/src/util/range.hpp @@ -35,6 +35,7 @@ #include <util/either.hpp> #include <util/iterutil.hpp> #include <util/meta.hpp> +#include <util/sentinel.hpp> namespace nest { namespace mc { @@ -66,7 +67,7 @@ struct range { range& operator=(const range&) = default; range& operator=(range&&) = default; - bool empty() const { return left==right; } + bool empty() const { return left == right; } iterator begin() const { return left; } const_iterator cbegin() const { return left; } @@ -77,10 +78,12 @@ struct range { template <typename V = iterator> enable_if_t<is_forward_iterator<V>::value, size_type> size() const { - return std::distance(begin(), end()); + return util::distance(begin(), end()); } - constexpr size_type max_size() const { return std::numeric_limits<size_type>::max(); } + constexpr size_type max_size() const { + return std::numeric_limits<size_type>::max(); + } void swap(range& other) { std::swap(left, other.left); @@ -148,164 +151,6 @@ 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(*(this->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(*(this->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))>> diff --git a/src/util/sentinel.hpp b/src/util/sentinel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8d08ecd41fe77f9c11ed08b8b539536792459c36 --- /dev/null +++ b/src/util/sentinel.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include <type_traits> + +#include <util/meta.hpp> + +/* + * 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. + */ + +namespace nest { +namespace mc { +namespace util { + +template<typename I, typename S, bool Same> +struct iterator_category_select { + // default category + using iterator_category = std::forward_iterator_tag; +}; + +template<typename I, typename S> +struct iterator_category_select<I,S,true> { + using iterator_category = typename std::iterator_traits<I>::iterator_category; +}; + +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 iterator_category_select< + I,S,std::is_same<I,S>::value>::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(*(this->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(*(this->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); +} + +} +} +} diff --git a/tests/unit/test_counter.cpp b/tests/unit/test_counter.cpp index 7c97f2277e08a865ebb0be0a7e7b9d4db18f7360..600d86ca171980ce6d17036d1d74936cafbd1ed0 100644 --- a/tests/unit/test_counter.cpp +++ b/tests/unit/test_counter.cpp @@ -128,4 +128,3 @@ REGISTER_TYPED_TEST_CASE_P(counter_test, value, compare, arithmetic, iterator_tr 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_range.cpp b/tests/unit/test_range.cpp index 7847212e3f0b0546c1f974dbdebab3303a15ea1d..5b1d84cb97dd72f0dd0cad36afdc27bc389437a8 100644 --- a/tests/unit/test_range.cpp +++ b/tests/unit/test_range.cpp @@ -11,14 +11,17 @@ #include <tbb/tbb_stddef.h> #endif +#include <util/counter.hpp> +#include <util/meta.hpp> #include <util/range.hpp> +#include <util/sentinel.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()); + auto s = util::make_range(l.begin(), l.end()); EXPECT_EQ(s.left, l.begin()); EXPECT_EQ(s.right, l.end()); @@ -40,6 +43,10 @@ TEST(range, list_iterator) { auto sum2 = std::accumulate(s.begin(), s.end(), 0); EXPECT_EQ(check, sum2); + + // Check that different begin/end iterators are treated correctly + auto sc = util::make_range(l.begin(), l.cend()); + EXPECT_EQ(l.size(), sc.size()); } TEST(range, pointer) { @@ -70,10 +77,59 @@ TEST(range, pointer) { EXPECT_TRUE(std::equal(s.begin(), s.end(), &xs[l])); } +TEST(range, empty) { + int xs[] = { 10, 11, 12, 13, 14, 15, 16 }; + auto l = 2; + auto r = 5; + + auto empty_range_ll = util::make_range((const int *) &xs[l], &xs[l]); + EXPECT_TRUE(empty_range_ll.empty()); + EXPECT_EQ(empty_range_ll.begin() == empty_range_ll.end(), + empty_range_ll.empty()); + EXPECT_EQ(0u, empty_range_ll.size()); + + + auto empty_range_rr = util::make_range(&xs[r], (const int *) &xs[r]); + EXPECT_TRUE(empty_range_rr.empty()); + EXPECT_EQ(empty_range_rr.begin() == empty_range_rr.end(), + empty_range_rr.empty()); + EXPECT_EQ(0u, empty_range_rr.size()); +} + +template<typename I, typename E, typename = void> +struct util_distance_enabled : public std::false_type {}; + +// This is the same test for enabling util::distance +template<typename I, typename E> +struct util_distance_enabled< + I, E, util::void_t< + util::enable_if_t< + !util::has_common_random_access_iterator<I, E>::value && + util::is_forward_iterator<I>::value + >>> : public std::true_type {}; + +TEST(range, size) { + static_assert(util_distance_enabled< + typename std::list<int>::iterator, + typename std::list<int>::const_iterator>::value, + "util::distance not enabled"); + static_assert(!util_distance_enabled< + typename std::vector<int>::const_iterator, + typename std::vector<int>::iterator>::value, + "util::distance erroneously enabled"); + static_assert(!util_distance_enabled<int*, int*>::value, + "util::distance erroneously enabled"); + static_assert(!util_distance_enabled<int*, const int*>::value, + "util::distance erroneously enabled"); + static_assert(!util_distance_enabled<const int*, int*>::value, + "util::distance erroneously enabled"); +} + 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>()); + auto s = util::make_range(std::istream_iterator<int>(sin), + std::istream_iterator<int>()); EXPECT_TRUE(std::equal(s.begin(), s.end(), &nums[0])); } @@ -115,6 +171,7 @@ TEST(range, sentinel) { } EXPECT_EQ(s, std::string(cstr)); + EXPECT_EQ(s.size(), cstr_range.size()); s.clear(); for (auto c: canonical_view(cstr_range)) { @@ -122,8 +179,100 @@ TEST(range, sentinel) { } EXPECT_EQ(s, std::string(cstr)); + + const char *empty_cstr = ""; + auto empty_cstr_range = util::make_range(empty_cstr, null_terminated); + EXPECT_TRUE(empty_cstr_range.empty()); + EXPECT_EQ(0u, empty_cstr_range.size()); } +template <typename V> +class counter_range: public ::testing::Test {}; + +TYPED_TEST_CASE_P(counter_range); + +TYPED_TEST_P(counter_range, max_size) { + using int_type = TypeParam; + using unsigned_int_type = typename std::make_unsigned<int_type>::type; + using counter = util::counter<int_type>; + + auto l = counter{int_type{1}}; + auto r = counter{int_type{10}}; + auto range = util::make_range(l, r); + EXPECT_EQ(std::numeric_limits<unsigned_int_type>::max(), + range.max_size()); + +} + +TYPED_TEST_P(counter_range, extreme_size) { + using int_type = TypeParam; + using signed_int_type = typename std::make_signed<int_type>::type; + using unsigned_int_type = typename std::make_unsigned<int_type>::type; + using counter = util::counter<signed_int_type>; + + auto l = counter{std::numeric_limits<signed_int_type>::min()}; + auto r = counter{std::numeric_limits<signed_int_type>::max()}; + auto range = util::make_range(l, r); + EXPECT_FALSE(range.empty()); + EXPECT_EQ(std::numeric_limits<unsigned_int_type>::max(), range.size()); + +} + + +TYPED_TEST_P(counter_range, size) { + using int_type = TypeParam; + using signed_int_type = typename std::make_signed<int_type>::type; + using counter = util::counter<signed_int_type>; + + auto l = counter{signed_int_type{-3}}; + auto r = counter{signed_int_type{3}}; + auto range = util::make_range(l, r); + EXPECT_FALSE(range.empty()); + EXPECT_EQ(6, range.size()); + +} + +TYPED_TEST_P(counter_range, at) { + using int_type = TypeParam; + using signed_int_type = typename std::make_signed<int_type>::type; + using counter = util::counter<signed_int_type>; + + auto l = counter{signed_int_type{-3}}; + auto r = counter{signed_int_type{3}}; + auto range = util::make_range(l, r); + EXPECT_EQ(range.front(), range.at(0)); + EXPECT_EQ(0, range.at(3)); + EXPECT_EQ(range.back(), range.at(range.size()-1)); + EXPECT_THROW(range.at(range.size()), std::out_of_range); + EXPECT_THROW(range.at(30), std::out_of_range); +} + +TYPED_TEST_P(counter_range, iteration) { + using int_type = TypeParam; + using signed_int_type = typename std::make_signed<int_type>::type; + using counter = util::counter<signed_int_type>; + + auto j = signed_int_type{-3}; + auto l = counter{signed_int_type{j}}; + auto r = counter{signed_int_type{3}}; + + for (auto i : util::make_range(l, r)) { + EXPECT_EQ(j++, i); + } +} + +REGISTER_TYPED_TEST_CASE_P(counter_range, max_size, extreme_size, size, at, + iteration); + +using int_types = ::testing::Types<signed char, unsigned char, short, + unsigned short, int, unsigned, long, + std::size_t, std::ptrdiff_t>; + +using signed_int_types = ::testing::Types<signed char, short, + int, long, std::ptrdiff_t>; + +INSTANTIATE_TYPED_TEST_CASE_P(int_types, counter_range, int_types); + #ifdef WITH_TBB TEST(range, tbb_split) {