/* * 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-2018 Danny Robson */ #ifndef CRUFT_UTIL_VIEW_HPP #define CRUFT_UTIL_VIEW_HPP #include "annotation.hpp" #include "cast.hpp" #include "types/traits.hpp" #include "maths.hpp" #include "platform.hpp" #include #include #include #include #include #include namespace cruft { template struct view { public: //--------------------------------------------------------------------- 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) { ; } template < typename ContainerT, typename = std::enable_if_t>,void> > view (ContainerT &rhs): view (rhs.begin (), rhs.end ()) { ; } template < typename ContainerT, typename = std::enable_if_t>,void> > view (const ContainerT &rhs): view (rhs.begin (), rhs.end ()) { ; } //--------------------------------------------------------------------- // cosntruction from pointer/size represenations for ease of use with // legacy C code. template < typename CountT, typename = std::enable_if_t,void> > 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): m_begin (const_cast (rhs.begin ())), m_end (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): m_begin (rhs.begin ()), m_end (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 = default; constexpr view (view &&) noexcept = default; view& operator= (const view &rhs) noexcept = default; view& operator= (view &&rhs) noexcept = 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::data (val) + std::size (val)) { ; } //--------------------------------------------------------------------- template view (const std::basic_string &val): view (std::data (val), std::data (val) + std::size (val)) { ; } //--------------------------------------------------------------------- template view (const std::vector &rhs): view (std::data (rhs), std::data (rhs) + std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (std::vector &rhs): view (std::data (rhs), std::data (rhs) + std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (std::array &rhs): view (std::data (rhs), std::data (rhs) + std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (const std::array &rhs): view (std::data (rhs), 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 (); } /////////////////////////////////////////////////////////////////////// constexpr bool empty (void) const noexcept { return m_begin == m_end; } //--------------------------------------------------------------------- // the return type of size _should_ be whatever std::distance returns, // or something else that makes sense for the iterators we've been // handed. // // but it's a pain in the arse to use sizes that aren't unsigned given // that other libraries need to compare sizes pretty often and // everything else in the world tends to be unsigned. constexpr auto size (void) const noexcept { return static_cast (std::distance (m_begin, m_end)); } //--------------------------------------------------------------------- [[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 }; }; //--------------------------------------------------------------------- [[nodiscard]] constexpr std::tuple< view, view > split (BeginT pos) const { return { { m_begin, pos }, { pos, m_end } }; } //--------------------------------------------------------------------- template [[nodiscard]] constexpr auto split (IndexT idx) const { 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 { 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 constexpr auto head (IndexT idx) { return std::get<0> (split (idx)); } //--------------------------------------------------------------------- template constexpr auto tail (IndexT idx) { return std::get<1> (split (idx)); } //--------------------------------------------------------------------- template [[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 () }; } constexpr auto operator+ (int count) const { return consume (count); } constexpr auto operator+= (int count)& { return *this = consume (count); } /////////////////////////////////////////////////////////////////////// template < typename ValueT, typename = std::enable_if_t< std::is_pointer_v && std::is_pointer_v > > view cast (void) const { return { cast::alignment (m_begin), cast::alignment (m_end) }; } /////////////////////////////////////////////////////////////////////// constexpr auto&& operator[] (size_t idx) noexcept { return *std::next (begin (), idx); } //--------------------------------------------------------------------- constexpr auto&& operator[] (size_t idx) const noexcept { 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; template view (const std::basic_string &) -> view; template view (std::vector&) -> view; template view (const std::vector&) -> view; template view (std::array&) -> view; template view (const std::array&) -> view; template view (ContainerT&) -> view< typename ContainerT::iterator >; template view (const ContainerT&) -> view< typename ContainerT::const_iterator >; // base + count constructor template view (BeginT, std::uint64_t count) -> view; template view (BeginT, std::uint32_t count) -> 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 byte 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 ByteT = std::byte, typename T > auto make_byte_view (T &t) { using cursor_type = std::conditional_t< std::is_const_v, ByteT const*, ByteT* >; return view { reinterpret_cast (&t), sizeof (T) }; } /////////////////////////////////////////////////////////////////////////// /// returns a reference to a value of the designated type at the front of /// the byte-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 ByteT, typename = std::enable_if_t > ValueT const& extract (view &buffer) { if (unlikely (sizeof (ValueT) > buffer.size ())) throw std::runtime_error ("insufficient data for extraction"); ValueT const &res = *cast::alignment (buffer.data ()); buffer = buffer.consume (sizeof (ValueT)); return res; } /////////////////////////////////////////////////////////////////////////// /// 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 ByteT, typename = std::enable_if_t > ValueT read (view &buffer) { // we disable the class-memaccess warning so that we can memcpy into // types that we know are safe but the compiler will complain about. // this occurs commonly with oddly packed structures, eg anything // that uses gnu::packed #if defined(COMPILER_GCC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" #endif if (unlikely (sizeof (ValueT) > buffer.size ())) throw std::runtime_error ("insufficient data for extraction"); ValueT res; memcpy (&res, buffer.data (), sizeof (ValueT)); buffer = buffer.consume (sizeof (ValueT)); return res; #if defined(COMPILER_GCC) #pragma GCC diagnostic pop #endif } /////////////////////////////////////////////////////////////////////////// template constexpr bool intersects (view a, IteratorT b) { return b >= a.begin () && b < a.end (); } /////////////////////////////////////////////////////////////////////////// template < typename BeginA, typename EndA, typename BeginB, typename EndB > constexpr bool equal (const view &a, const view &b) { return a.size () == b.size () && std::equal (std::begin (a), std::end (a), std::begin (b)); } //------------------------------------------------------------------------- // 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 bool operator== ( view lhs, const std::basic_string &rhs ) { return lhs.size () == rhs.size () && std::equal (lhs.cbegin (), lhs.cend (), rhs.cbegin ()); } inline bool operator== (view lhs, const char *rhs) { return lhs.size () == strlen (rhs) && std::equal (lhs.cbegin (), lhs.cend (), rhs); } /////////////////////////////////////////////////////////////////////////// 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); } } #endif