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) {