span.h raw

   1  // Copyright (c) 2018-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef LIMENKA_SPAN_H
   6  #define LIMENKA_SPAN_H
   7  
   8  #include <cassert>
   9  #include <cstddef>
  10  #include <span>
  11  #include <type_traits>
  12  #include <utility>
  13  
  14  #ifdef DEBUG
  15  #define CONSTEXPR_IF_NOT_DEBUG
  16  #define ASSERT_IF_DEBUG(x) assert((x))
  17  #else
  18  #define CONSTEXPR_IF_NOT_DEBUG constexpr
  19  #define ASSERT_IF_DEBUG(x)
  20  #endif
  21  
  22  #if defined(__clang__)
  23  #if __has_attribute(lifetimebound)
  24  #define SPAN_ATTR_LIFETIMEBOUND [[clang::lifetimebound]]
  25  #else
  26  #define SPAN_ATTR_LIFETIMEBOUND
  27  #endif
  28  #else
  29  #define SPAN_ATTR_LIFETIMEBOUND
  30  #endif
  31  
  32  /** A Span is an object that can refer to a contiguous sequence of objects.
  33   *
  34   * This file implements a subset of C++20's std::span.  It can be considered
  35   * temporary compatibility code until C++20 and is designed to be a
  36   * self-contained abstraction without depending on other project files. For this
  37   * reason, Clang lifetimebound is defined here instead of including
  38   * <attributes.h>, which also defines it.
  39   *
  40   * Things to be aware of when writing code that deals with Spans:
  41   *
  42   * - Similar to references themselves, Spans are subject to reference lifetime
  43   *   issues. The user is responsible for making sure the objects pointed to by
  44   *   a Span live as long as the Span is used. For example:
  45   *
  46   *       std::vector<int> vec{1,2,3,4};
  47   *       Span<int> sp(vec);
  48   *       vec.push_back(5);
  49   *       printf("%i\n", sp.front()); // UB!
  50   *
  51   *   may exhibit undefined behavior, as increasing the size of a vector may
  52   *   invalidate references.
  53   *
  54   * - One particular pitfall is that Spans can be constructed from temporaries,
  55   *   but this is unsafe when the Span is stored in a variable, outliving the
  56   *   temporary. For example, this will compile, but exhibits undefined behavior:
  57   *
  58   *       Span<const int> sp(std::vector<int>{1, 2, 3});
  59   *       printf("%i\n", sp.front()); // UB!
  60   *
  61   *   The lifetime of the vector ends when the statement it is created in ends.
  62   *   Thus the Span is left with a dangling reference, and using it is undefined.
  63   *
  64   * - Due to Span's automatic creation from range-like objects (arrays, and data
  65   *   types that expose a data() and size() member function), functions that
  66   *   accept a Span as input parameter can be called with any compatible
  67   *   range-like object. For example, this works:
  68   *
  69   *       void Foo(Span<const int> arg);
  70   *
  71   *       Foo(std::vector<int>{1, 2, 3}); // Works
  72   *
  73   *   This is very useful in cases where a function truly does not care about the
  74   *   container, and only about having exactly a range of elements. However it
  75   *   may also be surprising to see automatic conversions in this case.
  76   *
  77   *   When a function accepts a Span with a mutable element type, it will not
  78   *   accept temporaries; only variables or other references. For example:
  79   *
  80   *       void FooMut(Span<int> arg);
  81   *
  82   *       FooMut(std::vector<int>{1, 2, 3}); // Does not compile
  83   *       std::vector<int> baz{1, 2, 3};
  84   *       FooMut(baz); // Works
  85   *
  86   *   This is similar to how functions that take (non-const) lvalue references
  87   *   as input cannot accept temporaries. This does not work either:
  88   *
  89   *       void FooVec(std::vector<int>& arg);
  90   *       FooVec(std::vector<int>{1, 2, 3}); // Does not compile
  91   *
  92   *   The idea is that if a function accepts a mutable reference, a meaningful
  93   *   result will be present in that variable after the call. Passing a temporary
  94   *   is useless in that context.
  95   */
  96  template<typename C>
  97  class Span
  98  {
  99      C* m_data;
 100      std::size_t m_size{0};
 101  
 102      template <class T>
 103      struct is_Span_int : public std::false_type {};
 104      template <class T>
 105      struct is_Span_int<Span<T>> : public std::true_type {};
 106      template <class T>
 107      struct is_Span : public is_Span_int<typename std::remove_cv<T>::type>{};
 108  
 109  
 110  public:
 111      constexpr Span() noexcept : m_data(nullptr) {}
 112  
 113      /** Construct a span from a begin pointer and a size.
 114       *
 115       * This implements a subset of the iterator-based std::span constructor in C++20,
 116       * which is hard to implement without std::address_of.
 117       */
 118      template <typename T, typename std::enable_if<std::is_convertible<T (*)[], C (*)[]>::value, int>::type = 0>
 119      constexpr Span(T* begin, std::size_t size) noexcept : m_data(begin), m_size(size) {}
 120  
 121      /** Construct a span from a begin and end pointer.
 122       *
 123       * This implements a subset of the iterator-based std::span constructor in C++20,
 124       * which is hard to implement without std::address_of.
 125       */
 126      template <typename T, typename std::enable_if<std::is_convertible<T (*)[], C (*)[]>::value, int>::type = 0>
 127      CONSTEXPR_IF_NOT_DEBUG Span(T* begin, T* end) noexcept : m_data(begin), m_size(end - begin)
 128      {
 129          ASSERT_IF_DEBUG(end >= begin);
 130      }
 131  
 132      /** Implicit conversion of spans between compatible types.
 133       *
 134       *  Specifically, if a pointer to an array of type O can be implicitly converted to a pointer to an array of type
 135       *  C, then permit implicit conversion of Span<O> to Span<C>. This matches the behavior of the corresponding
 136       *  C++20 std::span constructor.
 137       *
 138       *  For example this means that a Span<T> can be converted into a Span<const T>.
 139       */
 140      template <typename O, typename std::enable_if<std::is_convertible<O (*)[], C (*)[]>::value, int>::type = 0>
 141      constexpr Span(const Span<O>& other) noexcept : m_data(other.m_data), m_size(other.m_size) {}
 142  
 143      /** Default copy constructor. */
 144      constexpr Span(const Span&) noexcept = default;
 145  
 146      /** Default assignment operator. */
 147      Span& operator=(const Span& other) noexcept = default;
 148  
 149      /** Construct a Span from an array. This matches the corresponding C++20 std::span constructor. */
 150      template <int N>
 151      constexpr Span(C (&a)[N]) noexcept : m_data(a), m_size(N) {}
 152  
 153      /** Construct a Span for objects with .data() and .size() (std::string, std::array, std::vector, ...).
 154       *
 155       * This implements a subset of the functionality provided by the C++20 std::span range-based constructor.
 156       *
 157       * To prevent surprises, only Spans for constant value types are supported when passing in temporaries.
 158       * Note that this restriction does not exist when converting arrays or other Spans (see above).
 159       */
 160      template <typename V>
 161      constexpr Span(V& other SPAN_ATTR_LIFETIMEBOUND,
 162          typename std::enable_if<!is_Span<V>::value &&
 163                                  std::is_convertible<typename std::remove_pointer<decltype(std::declval<V&>().data())>::type (*)[], C (*)[]>::value &&
 164                                  std::is_convertible<decltype(std::declval<V&>().size()), std::size_t>::value, std::nullptr_t>::type = nullptr)
 165          : m_data(other.data()), m_size(other.size()){}
 166  
 167      template <typename V>
 168      constexpr Span(const V& other SPAN_ATTR_LIFETIMEBOUND,
 169          typename std::enable_if<!is_Span<V>::value &&
 170                                  std::is_convertible<typename std::remove_pointer<decltype(std::declval<const V&>().data())>::type (*)[], C (*)[]>::value &&
 171                                  std::is_convertible<decltype(std::declval<const V&>().size()), std::size_t>::value, std::nullptr_t>::type = nullptr)
 172          : m_data(other.data()), m_size(other.size()){}
 173  
 174      constexpr C* data() const noexcept { return m_data; }
 175      constexpr C* begin() const noexcept { return m_data; }
 176      constexpr C* end() const noexcept { return m_data + m_size; }
 177      CONSTEXPR_IF_NOT_DEBUG C& front() const noexcept
 178      {
 179          ASSERT_IF_DEBUG(size() > 0);
 180          return m_data[0];
 181      }
 182      CONSTEXPR_IF_NOT_DEBUG C& back() const noexcept
 183      {
 184          ASSERT_IF_DEBUG(size() > 0);
 185          return m_data[m_size - 1];
 186      }
 187      constexpr std::size_t size() const noexcept { return m_size; }
 188      constexpr std::size_t size_bytes() const noexcept { return sizeof(C) * m_size; }
 189      constexpr bool empty() const noexcept { return size() == 0; }
 190      CONSTEXPR_IF_NOT_DEBUG C& operator[](std::size_t pos) const noexcept
 191      {
 192          ASSERT_IF_DEBUG(size() > pos);
 193          return m_data[pos];
 194      }
 195      CONSTEXPR_IF_NOT_DEBUG Span<C> subspan(std::size_t offset) const noexcept
 196      {
 197          ASSERT_IF_DEBUG(size() >= offset);
 198          return Span<C>(m_data + offset, m_size - offset);
 199      }
 200      CONSTEXPR_IF_NOT_DEBUG Span<C> subspan(std::size_t offset, std::size_t count) const noexcept
 201      {
 202          ASSERT_IF_DEBUG(size() >= offset + count);
 203          return Span<C>(m_data + offset, count);
 204      }
 205      CONSTEXPR_IF_NOT_DEBUG Span<C> first(std::size_t count) const noexcept
 206      {
 207          ASSERT_IF_DEBUG(size() >= count);
 208          return Span<C>(m_data, count);
 209      }
 210      CONSTEXPR_IF_NOT_DEBUG Span<C> last(std::size_t count) const noexcept
 211      {
 212           ASSERT_IF_DEBUG(size() >= count);
 213           return Span<C>(m_data + m_size - count, count);
 214      }
 215  
 216      template <typename O> friend class Span;
 217  };
 218  
 219  // Return result of calling .data() method on type T. This is used to be able to
 220  // write template deduction guides for the single-parameter Span constructor
 221  // below that will work if the value that is passed has a .data() method, and if
 222  // the data method does not return a void pointer.
 223  //
 224  // It is important to check for the void type specifically below, so the
 225  // deduction guides can be used in SFINAE contexts to check whether objects can
 226  // be converted to spans. If the deduction guides did not explicitly check for
 227  // void, and an object was passed that returned void* from data (like
 228  // std::vector<bool>), the template deduction would succeed, but the Span<void>
 229  // object instantiation would fail, resulting in a hard error, rather than a
 230  // SFINAE error.
 231  // https://stackoverflow.com/questions/68759148/sfinae-to-detect-the-explicitness-of-a-ctad-deduction-guide
 232  // https://stackoverflow.com/questions/16568986/what-happens-when-you-call-data-on-a-stdvectorbool
 233  template<typename T>
 234  using DataResult = std::remove_pointer_t<decltype(std::declval<T&>().data())>;
 235  
 236  // Deduction guides for Span
 237  // For the pointer/size based and iterator based constructor:
 238  template <typename T, typename EndOrSize> Span(T*, EndOrSize) -> Span<T>;
 239  // For the array constructor:
 240  template <typename T, std::size_t N> Span(T (&)[N]) -> Span<T>;
 241  // For the temporaries/rvalue references constructor, only supporting const output.
 242  template <typename T> Span(T&&) -> Span<std::enable_if_t<!std::is_lvalue_reference_v<T> && !std::is_void_v<DataResult<T&&>>, const DataResult<T&&>>>;
 243  // For (lvalue) references, supporting mutable output.
 244  template <typename T> Span(T&) -> Span<std::enable_if_t<!std::is_void_v<DataResult<T&>>, DataResult<T&>>>;
 245  
 246  /** Pop the last element off a span, and return a reference to that element. */
 247  template <typename T>
 248  T& SpanPopBack(Span<T>& span)
 249  {
 250      size_t size = span.size();
 251      T& back = span.back();
 252      span = span.first(size - 1);
 253      return back;
 254  }
 255  
 256  // From C++20 as_bytes and as_writeable_bytes
 257  template <typename T>
 258  Span<const std::byte> AsBytes(Span<T> s) noexcept
 259  {
 260      return {reinterpret_cast<const std::byte*>(s.data()), s.size_bytes()};
 261  }
 262  template <typename T>
 263  Span<std::byte> AsWritableBytes(Span<T> s) noexcept
 264  {
 265      return {reinterpret_cast<std::byte*>(s.data()), s.size_bytes()};
 266  }
 267  
 268  template <typename V>
 269  Span<const std::byte> MakeByteSpan(V&& v) noexcept
 270  {
 271      return AsBytes(Span{std::forward<V>(v)});
 272  }
 273  template <typename V>
 274  Span<std::byte> MakeWritableByteSpan(V&& v) noexcept
 275  {
 276      return AsWritableBytes(Span{std::forward<V>(v)});
 277  }
 278  
 279  // Helper functions to safely cast basic byte pointers to unsigned char pointers.
 280  inline unsigned char* UCharCast(char* c) { return reinterpret_cast<unsigned char*>(c); }
 281  inline unsigned char* UCharCast(unsigned char* c) { return c; }
 282  inline unsigned char* UCharCast(signed char* c) { return reinterpret_cast<unsigned char*>(c); }
 283  inline unsigned char* UCharCast(std::byte* c) { return reinterpret_cast<unsigned char*>(c); }
 284  inline const unsigned char* UCharCast(const char* c) { return reinterpret_cast<const unsigned char*>(c); }
 285  inline const unsigned char* UCharCast(const unsigned char* c) { return c; }
 286  inline const unsigned char* UCharCast(const signed char* c) { return reinterpret_cast<const unsigned char*>(c); }
 287  inline const unsigned char* UCharCast(const std::byte* c) { return reinterpret_cast<const unsigned char*>(c); }
 288  // Helper concept for the basic byte types.
 289  template <typename B>
 290  concept BasicByte = requires { UCharCast(std::span<B>{}.data()); };
 291  
 292  // Helper function to safely convert a Span to a Span<[const] unsigned char>.
 293  template <typename T> constexpr auto UCharSpanCast(Span<T> s) -> Span<typename std::remove_pointer<decltype(UCharCast(s.data()))>::type> { return {UCharCast(s.data()), s.size()}; }
 294  
 295  /** Like the Span constructor, but for (const) unsigned char member types only. Only works for (un)signed char containers. */
 296  template <typename V> constexpr auto MakeUCharSpan(V&& v) -> decltype(UCharSpanCast(Span{std::forward<V>(v)})) { return UCharSpanCast(Span{std::forward<V>(v)}); }
 297  
 298  #endif // LIMENKA_SPAN_H
 299