#ifndef _C4_SPAN_HPP_ #define _C4_SPAN_HPP_ /** @file span.hpp Provides span classes. */ #include "c4/config.hpp" #include "c4/error.hpp" #include "c4/szconv.hpp" #include namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") // NOLINTBEGIN(misc-confusable-identifiers) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** a crtp base for implementing span classes * * A span is a non-owning range of elements contiguously stored in memory. * Unlike STL's array_view, the span allows write-access to its members. * * To obtain subspans from a span, the following const member functions * are available: * - subspan(first, num) * - range(first, last) * - first(num) * - last(num) * * A span can also be resized via the following non-const member functions: * - resize(sz) * - ltrim(num) * - rtrim(num) * * @see span * @see cspan * @see spanrs * @see cspanrs * @see spanrsl * @see cspanrsl */ template class span_crtp { // some utility defines, undefined at the end of this class #define _c4this ((SpanImpl *)this) #define _c4cthis ((SpanImpl const*)this) #define _c4ptr ((SpanImpl *)this)->m_ptr #define _c4cptr ((SpanImpl const*)this)->m_ptr #define _c4sz ((SpanImpl *)this)->m_size #define _c4csz ((SpanImpl const*)this)->m_size public: _c4_DEFINE_ARRAY_TYPES(T, I); public: C4_ALWAYS_INLINE constexpr I value_size() const noexcept { return sizeof(T); } C4_ALWAYS_INLINE constexpr I elm_size () const noexcept { return sizeof(T); } C4_ALWAYS_INLINE constexpr I type_size () const noexcept { return sizeof(T); } C4_ALWAYS_INLINE I byte_size () const noexcept { return _c4csz*sizeof(T); } C4_ALWAYS_INLINE bool empty() const noexcept { return _c4csz == 0; } C4_ALWAYS_INLINE I size() const noexcept { return _c4csz; } //C4_ALWAYS_INLINE I capacity() const noexcept { return _c4sz; } // this must be defined by impl classes C4_ALWAYS_INLINE void clear() noexcept { _c4sz = 0; } C4_ALWAYS_INLINE T * data() noexcept { return _c4ptr; } C4_ALWAYS_INLINE T const* data() const noexcept { return _c4cptr; } C4_ALWAYS_INLINE iterator begin() noexcept { return _c4ptr; } C4_ALWAYS_INLINE const_iterator begin() const noexcept { return _c4cptr; } C4_ALWAYS_INLINE const_iterator cbegin() const noexcept { return _c4cptr; } C4_ALWAYS_INLINE iterator end() noexcept { return _c4ptr + _c4sz; } C4_ALWAYS_INLINE const_iterator end() const noexcept { return _c4cptr + _c4csz; } C4_ALWAYS_INLINE const_iterator cend() const noexcept { return _c4cptr + _c4csz; } C4_ALWAYS_INLINE reverse_iterator rbegin() noexcept { return reverse_iterator(_c4ptr + _c4sz); } C4_ALWAYS_INLINE const_reverse_iterator rbegin() const noexcept { return reverse_iterator(_c4cptr + _c4csz); } C4_ALWAYS_INLINE const_reverse_iterator crbegin() const noexcept { return reverse_iterator(_c4cptr + _c4csz); } C4_ALWAYS_INLINE reverse_iterator rend() noexcept { return const_reverse_iterator(_c4ptr); } C4_ALWAYS_INLINE const_reverse_iterator rend() const noexcept { return const_reverse_iterator(_c4cptr); } C4_ALWAYS_INLINE const_reverse_iterator crend() const noexcept { return const_reverse_iterator(_c4cptr); } C4_ALWAYS_INLINE T & front() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [0]; } C4_ALWAYS_INLINE T const& front() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[0]; } C4_ALWAYS_INLINE T & back() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [_c4sz - 1]; } C4_ALWAYS_INLINE T const& back() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[_c4csz - 1]; } C4_ALWAYS_INLINE T & operator[] (I i) C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4sz ); return _c4ptr [i]; } C4_ALWAYS_INLINE T const& operator[] (I i) const C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4csz); return _c4cptr[i]; } C4_ALWAYS_INLINE SpanImpl subspan(I first, I num) const C4_NOEXCEPT_X { C4_XASSERT((first >= 0 && first < _c4csz) || (first == _c4csz && num == 0)); C4_XASSERT((first + num >= 0) && (first + num <= _c4csz)); return _c4cthis->_select(_c4cptr + first, num); } C4_ALWAYS_INLINE SpanImpl subspan(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span { C4_XASSERT(first >= 0 && first <= _c4csz); return _c4cthis->_select(_c4cptr + first, _c4csz - first); } C4_ALWAYS_INLINE SpanImpl range(I first, I last) const C4_NOEXCEPT_X ///< last element is NOT included { C4_XASSERT(((first >= 0) && (first < _c4csz)) || (first == _c4csz && first == last)); C4_XASSERT((last >= 0) && (last <= _c4csz)); C4_XASSERT(last >= first); return _c4cthis->_select(_c4cptr + first, last - first); } C4_ALWAYS_INLINE SpanImpl range(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span { C4_XASSERT(((first >= 0) && (first <= _c4csz))); return _c4cthis->_select(_c4cptr + first, _c4csz - first); } C4_ALWAYS_INLINE SpanImpl first(I num) const C4_NOEXCEPT_X ///< get the first num elements, starting at 0 { C4_XASSERT((num >= 0) && (num <= _c4csz)); return _c4cthis->_select(_c4cptr, num); } C4_ALWAYS_INLINE SpanImpl last(I num) const C4_NOEXCEPT_X ///< get the last num elements, starting at size()-num { C4_XASSERT((num >= 0) && (num <= _c4csz)); return _c4cthis->_select(_c4cptr + _c4csz - num, num); } bool is_subspan(span_crtp const& ss) const noexcept { if(_c4cptr == nullptr) return false; auto *b = begin(), *e = end(); auto *ssb = ss.begin(), *sse = ss.end(); if(ssb >= b && sse <= e) { return true; } else { return false; } } /** COMPLement Left: return the complement to the left of the beginning of the given subspan. * If ss does not begin inside this, returns an empty substring. */ SpanImpl compll(span_crtp const& ss) const C4_NOEXCEPT_X { auto ssb = ss.begin(); auto b = begin(); auto e = end(); if(ssb >= b && ssb <= e) { return subspan(0, static_cast(ssb - b)); } else { return subspan(0, 0); } } /** COMPLement Right: return the complement to the right of the end of the given subspan. * If ss does not end inside this, returns an empty substring. */ SpanImpl complr(span_crtp const& ss) const C4_NOEXCEPT_X { auto sse = ss.end(); auto b = begin(); auto e = end(); if(sse >= b && sse <= e) { return subspan(static_cast(sse - b), static_cast(e - sse)); } else { return subspan(0, 0); } } C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const noexcept { return size() == that.size() && data() == that.data(); } template C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const C4_NOEXCEPT_X { I tsz = szconv(that.size()); // x-asserts that the size does not overflow return size() == tsz && data() == that.data(); } #undef _c4this #undef _c4cthis #undef _c4ptr #undef _c4cptr #undef _c4sz #undef _c4csz }; //----------------------------------------------------------------------------- // NOLINTBEGIN(*-redundant-inline*) template inline constexpr bool operator== ( span_crtp const& l, span_crtp const& r ) { #if C4_CPP >= 14 return std::equal(l.begin(), l.end(), r.begin(), r.end()); #else return l.same_span(r) || std::equal(l.begin(), l.end(), r.begin()); #endif } template inline constexpr bool operator!= ( span_crtp const& l, span_crtp const& r ) { return ! (l == r); } //----------------------------------------------------------------------------- template inline constexpr bool operator< ( span_crtp const& l, span_crtp const& r ) { return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); } template inline constexpr bool operator<= ( span_crtp const& l, span_crtp const& r ) { return ! (l > r); } //----------------------------------------------------------------------------- template inline constexpr bool operator> ( span_crtp const& l, span_crtp const& r ) { return r < l; } //----------------------------------------------------------------------------- template inline constexpr bool operator>= ( span_crtp const& l, span_crtp const& r ) { return ! (l < r); } // NOLINTEND(*-redundant-inline*) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A non-owning span of elements contiguously stored in memory. */ template class span : public span_crtp> // NOLINT(*-special-member-functions) { friend class span_crtp>; T * C4_RESTRICT m_ptr; I m_size; C4_ALWAYS_INLINE span _select(T *p, I sz) const { return span(p, sz); } public: _c4_DEFINE_ARRAY_TYPES(T, I); using NCT = typename std::remove_const::type; //!< NCT=non const type using CT = typename std::add_const::type; //!< CT=const type using const_type = span; /// convert automatically to span of const T operator span () const { span s(m_ptr, m_size); return s; } public: C4_ALWAYS_INLINE C4_CONSTEXPR14 span() noexcept : m_ptr{nullptr}, m_size{0} {} span(span const&) = default; span(span &&) = default; span& operator= (span const&) = default; span& operator= (span &&) = default; public: /** @name Construction and assignment from same type */ /** @{ */ template C4_ALWAYS_INLINE C4_CONSTEXPR14 span (T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N} {} template C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; } C4_ALWAYS_INLINE C4_CONSTEXPR14 span(T *p, I sz) noexcept : m_ptr{p}, m_size{sz} {} C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; } C4_ALWAYS_INLINE C4_CONSTEXPR14 span (c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{&*il.begin()}, m_size{il.size()} {} C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = &*il.begin(); m_size = il.size(); } /** @} */ public: C4_ALWAYS_INLINE I capacity() const noexcept { return m_size; } C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_size); m_size = sz; } C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; } }; template using cspan = span; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A non-owning span resizeable up to a capacity. Subselection or resizing * will keep the original provided it starts at begin(). If subselection or * resizing change the pointer, then the original capacity information will * be lost. * * Thus, resizing via resize() and ltrim() and subselecting via first() * or any of subspan() or range() when starting from the beginning will keep * the original capacity. OTOH, using last(), or any of subspan() or range() * with an offset from the start will remove from capacity (shifting the * pointer) by the corresponding offset. If this is undesired, then consider * using spanrsl. * * @see spanrs for a span resizeable on the right * @see spanrsl for a span resizeable on the right and left */ template class spanrs : public span_crtp> // NOLINT(*-special-member-functions) { friend class span_crtp>; T * C4_RESTRICT m_ptr; I m_size; I m_capacity; C4_ALWAYS_INLINE spanrs _select(T *p, I sz) const noexcept { C4_ASSERT(p >= m_ptr); size_t delta = static_cast(p - m_ptr); C4_ASSERT(m_capacity >= delta); return spanrs(p, sz, static_cast(m_capacity - delta)); } public: _c4_DEFINE_ARRAY_TYPES(T, I); using NCT = typename std::remove_const::type; //!< NCT=non const type using CT = typename std::add_const::type; //!< CT=const type using const_type = spanrs; /// convert automatically to span of T C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } /// convert automatically to span of const T //C4_ALWAYS_INLINE operator span () const noexcept { span s(m_ptr, m_size); return s; } /// convert automatically to spanrs of const T C4_ALWAYS_INLINE operator spanrs () const noexcept { spanrs s(m_ptr, m_size, m_capacity); return s; } public: C4_ALWAYS_INLINE spanrs() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0} {} spanrs(spanrs const&) = default; spanrs(spanrs &&) = default; spanrs& operator= (spanrs const&) = default; spanrs& operator= (spanrs &&) = default; public: /** @name Construction and assignment from same type */ /** @{ */ C4_ALWAYS_INLINE spanrs(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz} {} /** @warning will reset the capacity to sz */ C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; } C4_ALWAYS_INLINE spanrs(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap} {} C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; } template C4_ALWAYS_INLINE spanrs(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N} {} template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; } C4_ALWAYS_INLINE spanrs(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()} {} C4_ALWAYS_INLINE void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); } /** @} */ public: C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_capacity -= n; } }; template using cspanrs = spanrs; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A non-owning span which always retains the capacity of the original * range it was taken from (though it may loose its original size). * The resizing methods resize(), ltrim(), rtrim() as well * as the subselection methods subspan(), range(), first() and last() can be * used at will without loosing the original capacity; the full capacity span * can always be recovered by calling original(). */ template class spanrsl : public span_crtp> // NOLINT(*-special-member-functions) { friend class span_crtp>; T *C4_RESTRICT m_ptr; ///< the current ptr. the original ptr is (m_ptr - m_offset). I m_size; ///< the current size. the original size is unrecoverable. I m_capacity; ///< the current capacity. the original capacity is (m_capacity + m_offset). I m_offset; ///< the offset of the current m_ptr to the start of the original memory block. C4_ALWAYS_INLINE spanrsl _select(T *p, I sz) const noexcept { C4_ASSERT(p >= m_ptr); I delta = static_cast(p - m_ptr); C4_ASSERT(m_capacity >= delta); return spanrsl(p, sz, static_cast(m_capacity - delta), m_offset + delta); } public: _c4_DEFINE_ARRAY_TYPES(T, I); using NCT = typename std::remove_const::type; //!< NCT=non const type using CT = typename std::add_const::type; //!< CT=const type using const_type = spanrsl; C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } C4_ALWAYS_INLINE operator spanrs () const noexcept { return spanrs(m_ptr, m_size, m_capacity); } C4_ALWAYS_INLINE operator spanrsl () const noexcept { return spanrsl(m_ptr, m_size, m_capacity, m_offset); } public: C4_ALWAYS_INLINE spanrsl() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0}, m_offset{0} {} spanrsl(spanrsl const&) = default; spanrsl(spanrsl &&) = default; spanrsl& operator= (spanrsl const&) = default; spanrsl& operator= (spanrsl &&) = default; public: C4_ALWAYS_INLINE spanrsl(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz}, m_offset{0} {} C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; m_offset = 0; } C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{0} {} C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = 0; } C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap, I offs) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{offs} {} C4_ALWAYS_INLINE void assign(T *p, I sz, I cap, I offs) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = offs; } template C4_ALWAYS_INLINE spanrsl(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N}, m_offset{0} {} template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; m_offset = 0; } C4_ALWAYS_INLINE spanrsl(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()}, m_offset{0} {} C4_ALWAYS_INLINE void assign (c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); m_offset = 0; } public: C4_ALWAYS_INLINE I offset() const noexcept { return m_offset; } C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_offset += n; m_capacity -= n; } /** recover the original span as an spanrsl */ C4_ALWAYS_INLINE spanrsl original() const { return spanrsl(m_ptr - m_offset, m_capacity + m_offset, m_capacity + m_offset, 0); } /** recover the original span as a different span type. Example: spanrs<...> orig = s.original(); */ template class OtherSpanType> C4_ALWAYS_INLINE OtherSpanType original() { return OtherSpanType(m_ptr - m_offset, m_capacity + m_offset); } }; template using cspanrsl = spanrsl; // NOLINTEND(misc-confusable-identifiers) C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* _C4_SPAN_HPP_ */