/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2015-2019 Danny Robson */ #pragma once #include "annotation.hpp" #include "cast.hpp" #include "debug/assert.hpp" #include "maths.hpp" #include "platform.hpp" #include "types/traits.hpp" #include #include #include #include #include #include #include namespace cruft { template struct view { public: using begin_type = BeginT; using end_type = EndT; //--------------------------------------------------------------------- using value_type = typename std::iterator_traits< remove_restrict_t >::value_type; using size_type = size_t; //--------------------------------------------------------------------- constexpr view (const BeginT &first, const EndT &last) noexcept: m_begin (first), m_end (last) { if constexpr (cruft::is_lteq_orderable_v) { CHECK (m_begin <= m_end); } } template < typename ContainerT, typename = std::void_t< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) > > view (ContainerT &rhs) noexcept ( noexcept (std::declval ().begin ()) && noexcept (std::declval ().end ()) ) : view (rhs.begin (), rhs.end ()) { ; } template < typename ContainerT, typename = std::void_t< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) > > view (const ContainerT &rhs): view (rhs.begin (), rhs.end ()) { ; } //--------------------------------------------------------------------- // Construction from pointer/size representations for ease of use with // legacy C code. template < typename CountT, typename = std::enable_if_t,void> > constexpr view ( const BeginT &_begin, CountT _size ) : view (_begin, _begin + _size) { ; } //--------------------------------------------------------------------- // implicit conversion from const pointer const views to const pointer views template < typename ValueT, typename = std::enable_if_t< std::is_same_v && std::is_same_v > > view (const view &rhs): view ( const_cast (rhs.begin ()), const_cast (rhs.end ()) ) { ; } //--------------------------------------------------------------------- // implicit conversion from pointer views to const pointer views template < typename ValueT, typename = std::enable_if_t< std::is_same_v && std::is_same_v > > view (const view &rhs): view (rhs.begin (), rhs.end ()) { ; } //--------------------------------------------------------------------- // explicitly cater for the char array case so that we don't // accidentally include the trailing null in the data. template view (const char (&value)[N]): view {std::begin (value), std::begin (value) + N - 1} { static_assert (N > 0); } //--------------------------------------------------------------------- view (const char *str): view { str, str + strlen (str) } { ; } //--------------------------------------------------------------------- view (char *str): view (str, str + strlen (str)) { ; } //--------------------------------------------------------------------- template view (char (&value)[N]): view {std::begin (value), std::begin (value) + N - 1} { static_assert (N > 0); } //--------------------------------------------------------------------- template view (const ValueT(&value)[N]): view {std::begin (value), std::end (value)} { ; } //--------------------------------------------------------------------- template view (ValueT(&value)[N]): view {std::begin (value), std::end (value)} { ; } //--------------------------------------------------------------------- constexpr view (const view &) noexcept ( std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_constructible_v ) = default; constexpr view (view &&) noexcept ( std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v ) = default; view& operator= (view const &rhs) noexcept ( std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_assignable_v ) = default; view& operator= (view &&rhs) noexcept ( std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v ) = default; //--------------------------------------------------------------------- // allow null construction of views where IteratorT is constructible // from nullptr_t // // ideally we would avoid exposing this as it promotes use of nulls but // it simplifies construction of views that are data members of classes // when we may not immediately know the values we should contain. constexpr view (std::nullptr_t) noexcept: view {nullptr,nullptr} { ; } //--------------------------------------------------------------------- template view (std::basic_string &val): view (std::data (val), std::size (val)) { ; } //--------------------------------------------------------------------- template view (const std::basic_string &val): view (std::data (val), std::size (val)) { ; } //--------------------------------------------------------------------- template view (const std::vector &rhs): view (std::data (rhs), std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (std::vector &rhs): view (std::data (rhs), std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (std::array &rhs): view (std::data (rhs), std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (const std::array &rhs): view (std::data (rhs), std::size (rhs)) { ; } /////////////////////////////////////////////////////////////////////// constexpr BeginT begin (void) noexcept { return m_begin; } constexpr EndT end (void) noexcept { return m_end; } constexpr BeginT begin (void) const noexcept { return m_begin; } constexpr EndT end (void) const noexcept { return m_end; } //--------------------------------------------------------------------- constexpr BeginT cbegin (void) const noexcept { return m_begin; } constexpr EndT cend (void) const noexcept { return m_end; } //--------------------------------------------------------------------- auto data (void) noexcept { return begin (); } auto data (void) const noexcept { return begin (); } //--------------------------------------------------------------------- auto& front (void) noexcept { return *m_begin; } auto& front (void) const noexcept { return *m_begin; } //--------------------------------------------------------------------- auto& back (void) noexcept { return *(m_end - 1); } auto& back (void) const noexcept { return *(m_end - 1); } /////////////////////////////////////////////////////////////////////// /// Returns true if the size of the view is zero. constexpr bool empty (void) const noexcept { return m_begin == m_end; } ///-------------------------------------------------------------------- /// Returns true if the view is not empty; ie, there is data remaining. explicit constexpr operator bool () const noexcept { return not empty (); } ///-------------------------------------------------------------------- /// Returns the number of items in the view. constexpr auto size (void) const noexcept { return static_cast (std::distance (m_begin, m_end)); } ///-------------------------------------------------------------------- /// Returns a signed count of items in the view. constexpr ssize_t ssize (void) const noexcept { return std::distance (m_begin, m_end); } ///-------------------------------------------------------------------- /// Returns a subview of the first `count` elements of this view. [[nodiscard]] constexpr auto redim (size_type count) const { assert (count > 0); if (count > size ()) throw std::invalid_argument ("redim to higher size not allowed"); return view { m_begin, m_begin + count }; }; ///-------------------------------------------------------------------- /// Returns two subviews split at `pos`. /// /// The first view extends from `begin` to `pos`, and the second view /// extends from `pos` to `end`. [[nodiscard]] constexpr std::pair< view, view > split (BeginT pos) const { CHECK_GE (pos, m_begin); CHECK_LE (pos, m_end ); return { { m_begin, pos }, { pos, m_end } }; } ///-------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto split (IndexT idx) const { // It's ok if `idx` points to the end iterator; this just means the // second element of the returned pair is an empty view. static_assert ( std::numeric_limits::max () <= std::numeric_limits::max () ); CHECK_GE (idx, IndexT {0}); CHECK_LE (cruft::cast::lossless (idx), size ()); auto last = m_begin; std::advance (last, idx); return split (last); } //--------------------------------------------------------------------- // slices a view using python indexing semantics. ie, // "abc".slice(0, 3) == "abc" // "abc".slice(0, -1) == "abc" // "abc".slice(0, -2) == "ab" template [[nodiscard]] constexpr auto slice (IndexA a, IndexB b) const { CHECK_INCLUSIVE (cruft::abs (a), IndexA {0}, cruft::cast::lossless (size ())); CHECK_INCLUSIVE (cruft::abs (b), IndexB {0}, cruft::cast::lossless (size ())); auto first = m_begin; auto last = m_begin; std::advance (first, a < 0 ? size () + a + 1 : a); std::advance (last, b < 0 ? size () + b + 1 : b); return view { first, last }; } //--------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto head (IndexT idx) const { return std::get<0> (split (idx)); } //--------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto tail (IndexT idx) const { return std::get<1> (split (idx)); } //--------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto consume (IndexT count) const { auto [a,b] = split (count); (void)a; return b; } //--------------------------------------------------------------------- [[nodiscard]] constexpr view consume (view prefix) const { assert (prefix.begin () == begin ()); assert (prefix.end () < end ()); return { prefix.end (), end () }; } [[nodiscard]] constexpr view consume (const BeginT pos) const { return { pos, end () }; } /////////////////////////////////////////////////////////////////////// /// Explicitly cast to a view with different iterator types. /// /// The source and destination iterator types must be: /// * pointers /// * alignable /// /// It is undefined behaviour to cast with begin and/or end iterators /// that do not have natural alignment. Instrumented builds _may_ /// provide diagnostics or assertions in this case. /// /// \tparam ValueT The new iterator type /// \return A view that uses the new iterator type template < typename ValueT, typename = std::enable_if_t< // We can only convert views that use pointer iterators std::is_pointer_v && std::is_pointer_v && std::is_same_v && std::is_pointer_v > > view cast (void) const { // The values they point to must allow for alignment in one // direction or another. // // We prefer a static_assert over SFINAE because it reduces the // header burden for users (they do not need to include the // implementation of the pointer values to satisfy // iterator_traits), and it is quite unlikely we want to disable // this only if alignment is incompatible). static_assert ( sizeof (typename std::iterator_traits::value_type) % sizeof (typename std::iterator_traits::value_type) == 0 || sizeof (typename std::iterator_traits::value_type) % sizeof (typename std::iterator_traits::value_type) == 0 ); return { cast::alignment (m_begin), cast::alignment (m_end) }; } /////////////////////////////////////////////////////////////////////// constexpr auto&& operator[] (size_t idx) noexcept { CHECK_GE (idx, 0u); CHECK_LT (idx, size ()); return *std::next (begin (), idx); } //--------------------------------------------------------------------- constexpr auto&& operator[] (size_t idx) const noexcept { CHECK_GE (idx, 0u); CHECK_LT (idx, size ()); return *std::next (begin (), idx); } private: /////////////////////////////////////////////////////////////////////// BeginT m_begin; EndT m_end; }; //------------------------------------------------------------------------- template view (ValueT(&)[N]) -> view; //------------------------------------------------------------------------- view (const char*) -> view; view (char*) -> view; //------------------------------------------------------------------------- template < typename IteratorT, typename SizeT, typename = std::enable_if_t< std::is_integral_v > > view (IteratorT, SizeT) -> view; template view ( std::basic_string & ) -> view< typename std::allocator_traits::pointer >; template view ( const std::basic_string & ) -> view< typename std::allocator_traits::const_pointer >; template view ( std::vector& ) -> view::pointer>; template view ( const std::vector& ) -> view< typename std::allocator_traits::const_pointer >; template view (std::array&) -> view; template view (const std::array&) -> view; template view (ContainerT&) -> view< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) >; template view (const ContainerT&) -> view< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) >; // base + count constructor template view (BeginT, std::uint64_t count) -> view; template view (BeginT, std::uint32_t count) -> view; template view (IteratorT, int) -> view; /////////////////////////////////////////////////////////////////////////// template auto make_view (const ValueT (&arr)[N]) { return view (arr + 0, arr + N); } //------------------------------------------------------------------------- template auto make_view (ContainerT &t) { return view { std::begin (t), std::end (t) }; } //------------------------------------------------------------------------- template auto make_view (const ContainerT &t) { return view { std::cbegin (t), std::cend (t) }; } //------------------------------------------------------------------------- // disable the possibility of creating a view to a temporary. note that // this only works if an lval version has already been defined otherwise // universal reference rules will capture both lval and rval here. template auto make_view (ContainerT&&) = delete; /////////////////////////////////////////////////////////////////////////// template auto make_cview (const ContainerT &t) { return make_view (t); //return view { std::cbegin (t), std::cend (t) }; } //------------------------------------------------------------------------- template auto make_view (BeginT first, EndT last) { return view {first, last}; } //------------------------------------------------------------------------- template auto make_cview (ValueT *first, ValueT *last) { return view {first, last}; } /////////////////////////////////////////////////////////////////////////// inline view make_view (const char *str) { return { str, str + strlen (str) }; } //------------------------------------------------------------------------- inline view make_view (char *str) { return { str, str + strlen (str) }; } //------------------------------------------------------------------------- template view make_view (const std::basic_string &str) { return { std::data (str), std::data (str) + std::size (str) }; } //------------------------------------------------------------------------- template view make_view (std::basic_string &str) { return { std::data (str), std::data (str) + std::size (str) }; } //------------------------------------------------------------------------- template view make_view (const std::basic_string&&) = delete; //------------------------------------------------------------------------- template view make_view (std::basic_string&&) = delete; /////////////////////////////////////////////////////////////////////////// /// Calculates a word oriented view over an arbitrary type /// /// Useful for passing in memory structures to file descriptors and the /// like. but the consequences of endian conversion is on the user... /// /// We have to be careful that rval-references and other temporaries aren't /// accepted in this signature. template < typename WordT = std::byte const, typename T > cruft::view make_byte_view (T &t) { static_assert (sizeof (T) % sizeof (WordT) == 0); static_assert (std::is_const_v ? std::is_const_v : true); return view { cast::alignment (&t), sizeof (T) / sizeof (WordT) }; } /////////////////////////////////////////////////////////////////////////// /// Returns a reference to a value of the designated type at the front of /// the word-view. if there is insufficient data for the extraction an /// exception will be thrown. /// /// There are no validity or other checks performed on the returned data /// this is deliberate, so that the function is safe to call on user /// supplied data during parsing routines. it is up to the user to ensure /// the object is valid. /// /// The buffer object is advanced in place so that it no longer covers /// the extract value /// /// It is assumed the user has taken care of alignment concerns template < typename ValueT, typename WordT > ValueT& extract (view &buffer) { // Only allow calls if the value is a multiple of the word size. // It's useful to allow non-unit words for areas like TCP/IP which // tend to include protocols that utilise u16 words. static_assert ( sizeof (ValueT) % sizeof (WordT) == 0, "The value type must be a multiple of the word size" ); static_assert ( !std::is_const_v or std::is_const_v, "buffer and output types must have matching constness" ); if (unlikely (sizeof (ValueT) > buffer.size () * sizeof (WordT))) throw std::runtime_error ("insufficient data for extraction"); auto ptr = cast::alignment (buffer.data ()); buffer = buffer.consume (sizeof (ValueT) / sizeof (WordT)); return *ptr; } /////////////////////////////////////////////////////////////////////////// /// extracts an object of a specified type from the front of a byte-view. /// /// in contrast to 'extract' this will always copy the bytes out from the /// view, making the operation alignment safe. template < typename ValueT, typename WordT, typename = std::enable_if_t > ValueT read (view &buffer) { // We're going to use memcpy which requires that the type is // trivially copyable. static_assert (std::is_trivially_copyable_v); if (unlikely (sizeof (ValueT) > buffer.size () * sizeof (WordT))) throw std::runtime_error ("insufficient data for extraction"); std::aligned_storage_t bytes; memcpy (&bytes, buffer.data (), sizeof (ValueT)); buffer = buffer.consume (sizeof (ValueT) / sizeof (WordT)); return *reinterpret_cast (&bytes); } /////////////////////////////////////////////////////////////////////////// /// Tests whether an iterator falls within a given view. template constexpr bool intersects (view a, IteratorT b) { return b >= a.begin () && b < a.end (); } ///------------------------------------------------------------------------ /// Tests whether view `a` inclusively contains view `b`. template constexpr bool covers ( view const &a, view const &b ) { return a.begin () <= b.begin () && a.end () >= b.end (); } /////////////////////////////////////////////////////////////////////////// template < typename BeginA, typename EndA, typename BeginB, typename EndB, typename ComparatorT > decltype(auto) equal ( view const &a, view const &b, ComparatorT &&cmp ) { return std::equal ( std::begin (a), std::end (a), std::begin (b), std::end (b), std::forward (cmp) ); } //------------------------------------------------------------------------- template < typename BeginA, typename EndA, typename BeginB, typename EndB > constexpr bool equal (const view &a, const view &b) { return ::cruft::equal (a, b, std::equal_to {}); } //------------------------------------------------------------------------- // defer equality to the view/view operator by way of make_view template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool equal (const view &a, const ValueT &b) { return equal (a, make_view (b)); } //------------------------------------------------------------------------- // reverse the arguments and forward to the above operator. we formumlate // equality this way to avoid implementing the operator twice for each // weird case. template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool equal (const ValueT &a, const view &b) { return equal (b, a); } /////////////////////////////////////////////////////////////////////////// template constexpr bool operator== (const view &a, const view &b) { return a.begin () == b.begin () && a.end () == b.end (); } //------------------------------------------------------------------------- template constexpr bool operator!= (const view &a, const view &b) { return !(a == b); } //------------------------------------------------------------------------- template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool operator!= (const view &a, const ValueT &b) { return !(a == b); } //------------------------------------------------------------------------- template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool operator!= (const ValueT &a, const view &b) { return !(a == b); } /////////////////////////////////////////////////////////////////////////// template std::ostream& operator<< (std::ostream &os, view val) { std::copy ( std::cbegin (val), std::cend (val), std::ostream_iterator (os) ); return os; } /////////////////////////////////////////////////////////////////////////// /// a basic stringlike comparison operator that behaves as /// std::string::compare would. /// /// provided so that the common case of stringlike views can be used in a /// std::map and similar without a great deal of work. inline bool operator< (view a, view b) { const auto la = std::size (a); const auto lb = std::size (b); const auto res = strncmp ( std::data (a), std::data (b), min (la, lb) ); return res < 0 || (res == 0 && la < lb); } } namespace cruft::debug { /////////////////////////////////////////////////////////////////////////// template struct validator< view, ArgsT&&... > { static bool is_valid (view data, ArgsT &&...args) { return std::all_of ( std::begin (data), std::end (data), [&] (auto const &i) { return ::cruft::debug::is_valid (i, args...); }); } }; };