Skip to content
Snippets Groups Projects
  • Sam Yates's avatar
    Can't std::sort on forward iterators. · a50de751
    Sam Yates authored
    But GNU libstc++ tries to anyway, hilarious segfaults
    ensue.
    
    * Don't try and sort a sentinel-terminated range.
    * Instead provide a `strict_view` that walks a range
      (if required) and presents a range with a proper
      end iterator.
    * Tests, const range (but non-const iterator) implementations.
    a50de751
range.hpp 5.33 KiB
#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>
#include <util/sentinel.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 util::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);
    }

    auto front() const -> decltype(*left) { return *left; }

    auto back() const -> decltype(*left) { 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 constexpr 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);
}

// Present a possibly sentinel-terminated range as an STL-compatible sequence
// using the sentinel_iterator adaptor.

template <typename Seq>
auto canonical_view(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))};
}

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))};
}

// Strictly evaluate end point in sentinel-terminated range and present as a range over
// iterators. Note: O(N) behaviour with forward iterator ranges or sentinel-terminated ranges.

template <typename Seq>
auto strict_view(Seq& s) -> range<decltype(std::begin(s))>
{
    return make_range(std::begin(s), std::next(util::upto(std::begin(s), std::end(s))));
}

template <typename Seq>
auto strict_view(const Seq& s) -> range<decltype(std::begin(s))>
{
    return make_range(std::begin(s), std::next(util::upto(std::begin(s), std::end(s))));
}

} // namespace util
} // namespace mc
} // namespace nest