trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bc...@apache.org
Subject [trafficserver] branch master updated: MemSpan: Update to templated style, fix gcc9 compile error.
Date Thu, 09 May 2019 16:38:39 GMT
This is an automated email from the ASF dual-hosted git repository.

bcall pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 230c7bf  MemSpan: Update to templated style, fix gcc9 compile error.
230c7bf is described below

commit 230c7bfb1121aea774bdd6d36cec7cc5034c319b
Author: Alan M. Carroll <amc@apache.org>
AuthorDate: Wed May 1 16:55:34 2019 -0500

    MemSpan: Update to templated style, fix gcc9 compile error.
---
 include/tscore/BufferWriter.h                    |   4 +-
 include/tscore/MemArena.h                        |  20 +-
 include/tscpp/util/MemSpan.h                     | 879 +++++++++++++++--------
 lib/records/I_RecCore.h                          |   2 +-
 lib/records/P_RecMessage.h                       |   2 +-
 lib/records/RecHttp.cc                           |   2 +-
 lib/records/RecMessage.cc                        |   2 +-
 mgmt/BaseManager.cc                              |   2 +-
 mgmt/BaseManager.h                               |   6 +-
 mgmt/LocalManager.cc                             |   4 +-
 mgmt/MgmtDefs.h                                  |   2 +-
 mgmt/ProcessManager.cc                           |   9 +-
 proxy/logging/LogConfig.cc                       |   3 +-
 proxy/logging/LogConfig.h                        |   2 +-
 src/traffic_cache_tool/CacheDefs.cc              |  26 +-
 src/traffic_cache_tool/CacheDefs.h               |   6 +-
 src/traffic_cache_tool/CacheScan.cc              |   4 +-
 src/traffic_cache_tool/CacheScan.h               |   2 +-
 src/traffic_server/HostStatus.cc                 |   4 +-
 src/traffic_server/traffic_server.cc             |  19 +-
 src/tscore/BufferWriterFormat.cc                 |   4 +-
 src/tscore/MemArena.cc                           |   4 +-
 src/tscore/unit_tests/test_BufferWriterFormat.cc |  10 +-
 src/tscore/unit_tests/test_MemArena.cc           |  26 +-
 src/tscpp/util/unit_tests/test_MemSpan.cc        |  95 ++-
 25 files changed, 718 insertions(+), 421 deletions(-)

diff --git a/include/tscore/BufferWriter.h b/include/tscore/BufferWriter.h
index 42610fa..3e693a3 100644
--- a/include/tscore/BufferWriter.h
+++ b/include/tscore/BufferWriter.h
@@ -237,7 +237,7 @@ public:
   FixedBufferWriter(FixedBufferWriter &&)                 = delete;
   FixedBufferWriter &operator=(FixedBufferWriter &&) = delete;
 
-  FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}
+  FixedBufferWriter(MemSpan<char> &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}
 
   /// Write a single character @a c to the buffer.
   FixedBufferWriter &
@@ -734,7 +734,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, const void *ptr)
 }
 
 // MemSpan
-BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span);
+BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan<void> const &span);
 
 // -- Common formatters --
 
diff --git a/include/tscore/MemArena.h b/include/tscore/MemArena.h
index 8d0d8cc..fb98590 100644
--- a/include/tscore/MemArena.h
+++ b/include/tscore/MemArena.h
@@ -78,14 +78,14 @@ protected:
     size_t remaining() const;
 
     /// Span of unallocated storage.
-    MemSpan remnant();
+    MemSpan<void> remnant();
 
     /** Allocate @a n bytes from this block.
      *
      * @param n Number of bytes to allocate.
      * @return The span of memory allocated.
      */
-    MemSpan alloc(size_t n);
+    MemSpan<void> alloc(size_t n);
 
     /** Check if the byte at address @a ptr is in this block.
      *
@@ -129,7 +129,7 @@ public:
       @param n number of bytes to allocate.
       @return a MemSpan of the allocated memory.
    */
-  MemSpan alloc(size_t n);
+  MemSpan<void> alloc(size_t n);
 
   /** Allocate and initialize a block of memory.
 
@@ -183,7 +183,7 @@ public:
   size_t remaining() const;
 
   /// @returns the remaining contiguous space in the active generation.
-  MemSpan remnant() const;
+  MemSpan<void> remnant() const;
 
   /// @returns the total number of bytes allocated within the arena.
   size_t allocated_size() const;
@@ -259,11 +259,11 @@ MemArena::Block::remaining() const
   return size - allocated;
 }
 
-inline MemSpan
+inline MemSpan<void>
 MemArena::Block::alloc(size_t n)
 {
   ink_assert(n <= this->remaining());
-  MemSpan zret = this->remnant().prefix(n);
+  MemSpan<void> zret = this->remnant().prefix(n);
   allocated += n;
   return zret;
 }
@@ -277,10 +277,10 @@ MemArena::make(Args &&... args)
 
 inline MemArena::MemArena(size_t n) : _reserve_hint(n) {}
 
-inline MemSpan
+inline MemSpan<void>
 MemArena::Block::remnant()
 {
-  return {this->data() + allocated, static_cast<ptrdiff_t>(this->remaining())};
+  return {this->data() + allocated, this->remaining()};
 }
 
 inline size_t
@@ -301,10 +301,10 @@ MemArena::remaining() const
   return _active ? _active->remaining() : 0;
 }
 
-inline MemSpan
+inline MemSpan<void>
 MemArena::remnant() const
 {
-  return _active ? _active->remnant() : MemSpan{};
+  return _active ? _active->remnant() : MemSpan<void>{};
 }
 
 inline size_t
diff --git a/include/tscpp/util/MemSpan.h b/include/tscpp/util/MemSpan.h
index 9779e02..1c757b3 100644
--- a/include/tscpp/util/MemSpan.h
+++ b/include/tscpp/util/MemSpan.h
@@ -1,13 +1,11 @@
 /** @file
 
-   Spans of memory. This is similar but independently developed from @c std::span. The goal is
-   to provide convenient handling for chunks of memory. These chunks can be treated as arrays
-   of arbitrary types via template methods.
+   Spans of writable memory. This is similar but independently developed from @c std::span. The goal
+   is to provide convenient handling for chunks of memory. These chunks can be treated as arrays of
+   arbitrary types via template methods.
+*/
 
-
-   @section license License
-
-   Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
    agreements.  See the NOTICE file distributed with this work for additional information regarding
    copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
    (the "License"); you may not use this file except in compliance with the License.  You may obtain
@@ -27,57 +25,60 @@
 #include <iostream>
 #include <cstddef>
 #include <string_view>
+#include <type_traits>
+#include <ratio>
+#include <exception>
 
-/// Apache Traffic Server commons.
 namespace ts
 {
 /** A span of contiguous piece of memory.
 
     A @c MemSpan does not own the memory to which it refers, it is simply a span of part of some
-    (presumably) larger memory object. The purpose is that frequently code needs to work on a specific
-    part of the memory. This can avoid copying or allocation by allocating all needed memory at once
-    and then working with it via instances of this class.
+    (presumably) larger memory object. It acts as a pointer, not a container - copy and assignment
+    change the span, not the memory to which the span refers.
+
+    The purpose is that frequently code needs to work on a specific part of the memory. This can
+    avoid copying or allocation by allocating all needed memory at once and then working with it via
+    instances of this class.
+
  */
-class MemSpan
+template <typename T> class MemSpan
 {
   using self_type = MemSpan; ///< Self reference type.
 
 protected:
-  void *_data     = nullptr; ///< Pointer to base of memory chunk.
-  ptrdiff_t _size = 0;       ///< Size of memory chunk.
+  T *_ptr       = nullptr; ///< Pointer to base of memory chunk.
+  size_t _count = 0;       ///< Number of elements.
 
 public:
+  using value_type = T;
+
   /// Default constructor (empty buffer).
-  constexpr MemSpan();
+  constexpr MemSpan() = default;
 
-  /** Construct explicitly with a pointer and size.
-   */
-  constexpr MemSpan(void *ptr,  ///< Pointer to buffer.
-                    ptrdiff_t n ///< Size of buffer.
-  );
+  /// Copy constructor.
+  constexpr MemSpan(self_type const &that) = default;
 
-  /** Construct from a half open range of two pointers.
-      @note The instance at @start is in the span but the instance at @a end is not.
-  */
-  template <typename T>
-  constexpr MemSpan(T *start, ///< First byte in the span.
-                    T *end    ///< First byte not in the span.
-  );
+  /** Construct from a first element @a start and a @a count of elements.
+   *
+   * @param start First element.
+   * @param count Total number of elements.
+   */
+  constexpr MemSpan(value_type *start, size_t count);
 
-  /** Construct from a half open range of two pointers.
-      @note The instance at @start is in the span but the instance at @a end is not.
-  */
-  MemSpan(void *start, ///< First byte in the span.
-          void *end    ///< First byte not in the span.
-  );
+  /** Construct from a half open range [start, last).
+   *
+   * @param start Start of range.
+   * @param last Past end of range.
+   */
+  constexpr MemSpan(value_type *start, value_type *last);
 
   /** Construct to cover an array.
    *
-   * @tparam T Array element type.
    * @tparam N Number of elements in the array.
    * @param a The array.
    */
-  template <typename T, size_t N> MemSpan(T (&a)[N]);
+  template <size_t N> MemSpan(T (&a)[N]);
 
   /** Construct from nullptr.
       This implicitly makes the length 0.
@@ -107,17 +108,10 @@ public:
   bool operator!=(self_type const &that) const;
 
   /// Assignment - the span is copied, not the content.
-  self_type &operator=(self_type const &that);
+  self_type &operator=(self_type const &that) = default;
 
-  /** Shift the span to discard the first byte.
-      @return @a this.
-  */
-  self_type &operator++();
-
-  /** Shift the span to discard the leading @a n bytes.
-      @return @a this
-  */
-  self_type &operator+=(ptrdiff_t n);
+  /// Access element at index @a idx.
+  T &operator[](size_t idx) const;
 
   /// Check for empty span.
   /// @return @c true if the span is empty (no contents), @c false otherwise.
@@ -133,167 +127,344 @@ public:
 
   /// @name Accessors.
   //@{
-  /// Pointer to the first byte in the span.
-  char *begin();
-  const char *begin() const;
+  /// Pointer to the first element in the span.
+  T *begin() const;
+
+  /// Pointer to first element not in the span.
+  T *end() const;
 
-  /// Pointer to first byte not in the span.
-  char *end();
-  const char *end() const;
+  /// Number of elements in the span
+  size_t count() const;
 
   /// Number of bytes in the span.
-  constexpr ptrdiff_t ssize() const;
   size_t size() const;
 
   /// Pointer to memory in the span.
-  void *data();
-
-  /// Pointer to memory in the span.
-  const void *data() const;
-
-  /// Memory pointer, one past the last element of the span.
-  void *data_end();
-  const void *data_end() const;
-
-  /// @return the @a V value at index @a n.
-  template <typename V> V at(ptrdiff_t n) const;
+  T *data() const;
 
-  /// @return a pointer to the @a V value at index @a n.
-  template <typename V> V const *ptr(ptrdiff_t n) const;
-  //@}
+  /** Make a copy of @a this span on the same memory but of type @a U.
+   *
+   * @tparam U Type for the created span.
+   * @return A @c MemSpan which contains the same memory as instances of @a U.
+   */
+  template <typename U = void> MemSpan<U> rebind() const;
 
   /// Set the span.
   /// This is faster but equivalent to constructing a new span with the same
   /// arguments and assigning it.
   /// @return @c this.
-  self_type &assign(void *ptr,      ///< Buffer address.
-                    ptrdiff_t n = 0 ///< Buffer size.
+  self_type &assign(T *ptr,      ///< Buffer start.
+                    size_t count ///< # of elements.
   );
 
   /// Set the span.
   /// This is faster but equivalent to constructing a new span with the same
   /// arguments and assigning it.
   /// @return @c this.
-  self_type &assign(void *start,    ///< First valid character.
-                    void const *end ///< First invalid character.
+  self_type &assign(T *first,     ///< First valid element.
+                    T const *last ///< First invalid element.
   );
 
   /// Clear the span (become an empty span).
   self_type &clear();
 
   /// @return @c true if the byte at @a *p is in the span.
-  bool contains(const void *p) const;
+  bool contains(value_type const *p) const;
 
-  /** Find a value.
-      The memory is searched as if it were an array of the value type @a V.
+  /** Get the initial segment of @a count elements.
 
-      @return A pointer to the first occurrence of @a v in @a this
-      or @c nullptr if @a v is not found.
+      @return An instance that contains the leading @a count elements of @a this.
   */
-  template <typename V> V *find(V v) const;
+  self_type prefix(size_t count) const;
 
-  /** Find a value.
-      The memory is searched as if it were an array of type @a V.
+  /** Shrink the span by removing @a count leading elements.
+   *
+   * @param count The number of elements to remove.
+   * @return @c *this
+   */
+  self_type &remove_prefix(size_t count);
 
-      @return A pointer to the first value for which @a pred is @c true otherwise
-      @c nullptr.
-  */
-  template <typename V, typename F> V *find_if(F const &pred);
+  /** Get the trailing segment of @a count elements.
+   *
+   * @param count Number of elements to retrieve.
+   * @return An instance that contains the trailing @a count elements of @a this.
+   */
+  self_type suffix(size_t count) const;
 
-  /** Get the initial segment of the span before @a p.
+  /** Shrink the span by removing @a count trailing elements.
+   *
+   * @param count Number of elements to remove.
+   * @return @c *this
+   */
+  self_type &remove_suffix(size_t count);
 
-      The byte at @a p is not included. If @a p is not in the span an empty span
-      is returned.
+  /** Return a view of the memory.
+   *
+   * @return A @c string_view covering the span contents.
+   */
+  std::string_view view() const;
 
-      @return A buffer that contains all data before @a p.
-  */
-  self_type prefix(const void *p) const;
+  template <typename U> friend class MemSpan;
+};
 
-  /** Get the first @a n bytes of the span.
+/** Specialization for void pointers.
+ *
+ * Key differences:
+ *
+ * - No subscript operator.
+ * - No array initialization.
+ * - All other @c MemSpan types implicitly convert to this type.
+ *
+ * @internal I tried to be clever about the base template but there were too many differences
+ * One major issue was the array initialization did not work at all if the @c void case didn't
+ * exclude that. Once separate there are a number of useful tweaks available.
+ */
+template <> class MemSpan<void>
+{
+  using self_type = MemSpan; ///< Self reference type.
+  template <typename U> friend class MemSpan;
 
-      @return A span with the first @a n bytes of this span.
-  */
-  self_type prefix(ptrdiff_t n) const;
+public:
+  using value_type = void; /// Export base type.
 
-  /** Shrink the span from the front.
+protected:
+  value_type *_ptr = nullptr; ///< Pointer to base of memory chunk.
+  size_t _size     = 0;       ///< Number of elements.
+
+public:
+  /// Default constructor (empty buffer).
+  constexpr MemSpan() = default;
+
+  /// Copy constructor.
+  constexpr MemSpan(self_type const &that) = default;
+
+  /** Cross type copy constructor.
    *
-   * @param p The limit of the removed span.
-   * @return @c *this
+   * @tparam U Type for source span.
+   * @param that Source span.
    *
-   * The byte at @a p is not removed.
+   * This enables any @c MemSpan to be automatically converted to a void span, just as any pointer
+   * can convert to a void pointer.
    */
+  template <typename U> constexpr MemSpan(MemSpan<U> const &that);
 
-  self_type remove_prefix(void const *p);
-  /** Shrink the span from the front.
+  /** Construct from a pointer @a start and a size @a n bytes.
    *
-   * @param n The number of bytes to remove.
-   * @return @c *this
+   * @param start Start of the span.
+   * @param n # of bytes in the span.
    */
-  self_type &remove_prefix(ptrdiff_t n);
-
-  /** Get the trailing segment of the span after @a p.
+  constexpr MemSpan(value_type *start, size_t n);
 
-      The byte at @a p is not included. If @a p is not in the span an empty span is returned.
+  /** Construct from a half open range of [start, last).
+   *
+   * @param start Start of the range.
+   * @param last Past end of range.
+   */
+  MemSpan(value_type *start, value_type *last);
 
-      @return A buffer that contains all data after @a p.
+  /** Construct from nullptr.
+      This implicitly makes the length 0.
   */
-  self_type suffix(const void *p) const;
+  constexpr MemSpan(std::nullptr_t);
+
+  /** Equality.
 
-  /** Get the trailing @a n bytes.
+      Compare the span contents.
+
+      @return @c true if the contents of @a that are bytewise the same as the content of @a this,
+      @c false otherwise.
+   */
+  bool operator==(self_type const &that) const;
+
+  /** Identical.
+
+      Check if the spans refer to the same span of memory.
+
+      @return @c true if @a this and @a that refer to the same memory, @c false if not.
+   */
+  bool is_same(self_type const &that) const;
+
+  /** Inequality.
+      @return @c true if @a that does not refer to the same span as @a this,
+      @c false otherwise.
+   */
+  bool operator!=(self_type const &that) const;
+
+  /// Assignment - the span is copied, not the content.
+  /// Any type of @c MemSpan can be assigned to @c MemSpan<void>.
+  template <typename U> self_type &operator=(MemSpan<U> const &that);
+
+  /// Check for empty span.
+  /// @return @c true if the span is empty (no contents), @c false otherwise.
+  bool operator!() const;
+
+  /// Check for non-empty span.
+  /// @return @c true if the span contains bytes.
+  explicit operator bool() const;
+
+  /// Check for empty span (no content).
+  /// @see operator bool
+  bool empty() const;
 
-      @return A span with @a n bytes of the current span.
+  /// Number of bytes in the span.
+  size_t size() const;
+
+  /// Pointer to memory in the span.
+  value_type *data() const;
+
+  /// Pointer to memory in the span.
+  value_type *data_end() const;
+
+  /** Create a new span for a different type @a V on the same memory.
+   *
+   * @tparam V Type for the created span.
+   * @return A @c MemSpan which contains the same memory as instances of @a V.
+   */
+  template <typename U> MemSpan<U> rebind() const;
+
+  /// Set the span.
+  /// This is faster but equivalent to constructing a new span with the same
+  /// arguments and assigning it.
+  /// @return @c this.
+  self_type &assign(value_type *ptr, ///< Buffer start.
+                    size_t n         ///< # of bytes
+  );
+
+  /// Set the span.
+  /// This is faster but equivalent to constructing a new span with the same
+  /// arguments and assigning it.
+  /// @return @c this.
+  self_type &assign(value_type *first,     ///< First valid element.
+                    value_type const *last ///< First invalid element.
+  );
+
+  /// Clear the span (become an empty span).
+  self_type &clear();
+
+  /// @return @c true if the byte at @a *ptr is in the span.
+  bool contains(value_type const *ptr) const;
+
+  /** Get the initial segment of @a n bytes.
+
+      @return An instance that contains the leading @a n bytes of @a this.
   */
-  self_type suffix(ptrdiff_t p) const;
+  self_type prefix(size_t n) const;
 
-  /** Shrink the span from the back.
+  /** Shrink the span by removing @a n leading bytes.
    *
-   * @param p The limit of the removed span.
+   * @param count The number of elements to remove.
    * @return @c *this
+   */
+
+  self_type &remove_prefix(size_t count);
+
+  /** Get the trailing segment of @a n bytes.
    *
-   * The byte at @a p is not removed.
+   * @param n Number of bytes to retrieve.
+   * @return An instance that contains the trailing @a count elements of @a this.
    */
-  self_type &remove_suffix(void const *p);
+  self_type suffix(size_t n) const;
 
-  /** Shrink the span from the back.
+  /** Shrink the span by removing @a n bytes.
    *
-   * @param n The number of bytes to remove.
+   * @param n Number of bytes to remove.
    * @return @c *this
    */
-  self_type &remove_suffix(ptrdiff_t n);
+  self_type &remove_suffix(size_t n);
 
   /** Return a view of the memory.
    *
    * @return A @c string_view covering the span contents.
    */
   std::string_view view() const;
+};
 
-  /** Support automatic conversion to string_view.
+// -- Implementation --
+
+namespace detail
+{
+  /// Suport pointer distance calculations for all types, @b include @c <void*>.
+  /// This is useful in templates.
+  inline size_t
+  ptr_distance(void const *first, void const *last)
+  {
+    return static_cast<const char *>(last) - static_cast<const char *>(first);
+  }
+
+  template <typename T>
+  size_t
+  ptr_distance(T const *first, T const *last)
+  {
+    return last - first;
+  }
+
+  /** Functor to convert span types.
    *
-   * @return A view of the memory in this span.
+   * @tparam T Source span type.
+   * @tparam U Destination span type.
+   *
+   * @internal More void handling. This can't go in @c MemSpan because template specialization is
+   * invalid in class scope and this needs to be specialized for @c void.
    */
-  operator std::string_view() const;
+  template <typename T, typename U> struct is_span_compatible {
+    /// @c true if the size of @a T is an integral multiple of the size of @a U or vice versa.
+    static constexpr bool value = std::ratio<sizeof(T), sizeof(U)>::num == 1 || std::ratio<sizeof(U), sizeof(T)>::num == 1;
+    /** Compute the new size in units of @c sizeof(U).
+     *
+     * @param size Size in bytes.
+     * @return Size in units of @c sizeof(U).
+     *
+     * The critical part of this is the @c static_assert that guarantees the result is an integral
+     * number of instances of @a U.
+     */
+    static size_t count(size_t size);
+  };
+
+  template <typename T, typename U>
+  size_t
+  is_span_compatible<T, U>::count(size_t size)
+  {
+    if (size % sizeof(U)) {
+      throw std::invalid_argument("MemSpan rebind where span size is not a multiple of the element size");
+    }
+    return size / sizeof(U);
+  }
 
-  /// Internal utility for computing the difference of two void pointers.
-  /// @return the byte (char) difference between the pointers, @a lhs - @a rhs
-  static ptrdiff_t distance(void const *lhs, void const *rhs);
-};
+  /// @cond INTERNAL_DETAIL
+  // Must specialize for rebinding to @c void because @c sizeof doesn't work. Rebinding from @c void
+  // is handled by the @c MemSpan<void>::rebind specialization and doesn't use this mechanism.
+  template <typename T> struct is_span_compatible<T, void> {
+    static constexpr bool value = true;
+    static size_t count(size_t size);
+  };
 
-// -- Implementation --
+  template <typename T>
+  size_t
+  is_span_compatible<T, void>::count(size_t size)
+  {
+    return size;
+  }
+  /// @endcond
+
+} // namespace detail
+
+// --- Standard memory operations ---
 
-inline int
-memcmp(MemSpan const &lhs, MemSpan const &rhs)
+template <typename T>
+int
+memcmp(MemSpan<T> const &lhs, MemSpan<T> const &rhs)
 {
-  int zret    = 0;
-  ptrdiff_t n = lhs.size();
+  int zret = 0;
+  size_t n = lhs.size();
 
   // Seems a bit ugly but size comparisons must be done anyway to get the memcmp args.
-  if (lhs.size() < rhs.size()) {
+  if (lhs.count() < rhs.count()) {
     zret = 1;
-  } else if (lhs.size() > rhs.size()) {
+  } else if (lhs.count() > rhs.count()) {
     zret = -1;
     n    = rhs.size();
   }
-  // else the sizes are equal therefore @a n and @a zret are already correct.
+  // else the counts are equal therefore @a n and @a zret are already correct.
 
   int r = std::memcmp(lhs.data(), rhs.data(), n);
   if (0 != r) { // If we got a not-equal, override the size based result.
@@ -302,317 +473,399 @@ memcmp(MemSpan const &lhs, MemSpan const &rhs)
 
   return zret;
 }
-// need to bring memcmp in so this is an overload, not an override.
+
 using std::memcmp;
 
-inline constexpr MemSpan::MemSpan() {}
+template <typename T>
+T *
+memcpy(MemSpan<T> &dst, MemSpan<T> const &src)
+{
+  return static_cast<T *>(std::memcpy(dst.data(), src.data(), std::min(dst.size(), src.size())));
+}
 
-inline constexpr MemSpan::MemSpan(void *ptr, ptrdiff_t n) : _data(ptr), _size(n) {}
+template <typename T>
+T *
+memcpy(MemSpan<T> &dst, T *src)
+{
+  return static_cast<T *>(std::memcpy(dst.data(), src, dst.size()));
+}
 
-template <typename T> constexpr MemSpan::MemSpan(T *start, T *end) : _data(start), _size((end - start) * sizeof(T)) {}
+template <typename T>
+T *
+memcpy(T *dst, MemSpan<T> &src)
+{
+  return static_cast<T *>(std::memcpy(dst, src.data(), src.size()));
+}
 
-// <void*> is magic, handle that specially.
-// No constexpr because the spec specifically forbids casting from <void*> to a typed pointer.
-inline MemSpan::MemSpan(void *start, void *end) : _data(start), _size(static_cast<char *>(end) - static_cast<char *>(start)) {}
+inline char *
+memcpy(MemSpan<char> &span, std::string_view view)
+{
+  return static_cast<char *>(std::memcpy(span.data(), view.data(), std::min(view.size(), view.size())));
+}
 
-template <typename T, size_t N> MemSpan::MemSpan(T (&a)[N]) : _data(a), _size(N * sizeof(T)) {}
+inline void *
+memcpy(MemSpan<void> &span, std::string_view view)
+{
+  return std::memcpy(span.data(), view.data(), std::min(view.size(), view.size()));
+}
 
-inline constexpr MemSpan::MemSpan(std::nullptr_t) {}
+using std::memcpy;
+using std::memcpy;
 
-inline ptrdiff_t
-MemSpan::distance(void const *lhs, void const *rhs)
+template <typename T>
+inline MemSpan<T> const &
+memset(MemSpan<T> const &dst, T const &t)
 {
-  return static_cast<const char *>(lhs) - static_cast<const char *>(rhs);
+  for (auto &e : dst) {
+    e = t;
+  }
+  return dst;
 }
 
-inline MemSpan &
-MemSpan::assign(void *ptr, ptrdiff_t n)
+inline MemSpan<char> const &
+memset(MemSpan<char> const &dst, char c)
 {
-  _data = ptr;
-  _size = n;
+  std::memset(dst.data(), c, dst.size());
+  return dst;
+}
+
+inline MemSpan<unsigned char> const &
+memset(MemSpan<unsigned char> const &dst, unsigned char c)
+{
+  std::memset(dst.data(), c, dst.size());
+  return dst;
+}
+
+inline MemSpan<void> const &
+memset(MemSpan<void> const &dst, char c)
+{
+  std::memset(dst.data(), c, dst.size());
+  return dst;
+}
+
+using std::memset;
+
+// --- MemSpan<T> ---
+
+template <typename T> constexpr MemSpan<T>::MemSpan(T *ptr, size_t count) : _ptr{ptr}, _count{count} {}
+
+template <typename T> constexpr MemSpan<T>::MemSpan(T *first, T *last) : _ptr{first}, _count{detail::ptr_distance(first, last)} {}
+
+template <typename T> template <size_t N> MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{a}, _count{N} {}
+
+template <typename T> constexpr MemSpan<T>::MemSpan(std::nullptr_t) {}
+
+template <typename T>
+MemSpan<T> &
+MemSpan<T>::assign(T *ptr, size_t count)
+{
+  _ptr   = ptr;
+  _count = count;
   return *this;
 }
 
-inline MemSpan &
-MemSpan::assign(void *ptr, void const *limit)
+template <typename T>
+MemSpan<T> &
+MemSpan<T>::assign(T *first, T const *last)
 {
-  _data = ptr;
-  _size = static_cast<const char *>(limit) - static_cast<const char *>(ptr);
+  _ptr   = first;
+  _count = detail::ptr_distance(first, last);
   return *this;
 }
 
-inline MemSpan &
-MemSpan::clear()
+template <typename T>
+MemSpan<T> &
+MemSpan<T>::clear()
 {
-  _data = nullptr;
-  _size = 0;
+  _ptr   = nullptr;
+  _count = 0;
   return *this;
 }
 
-inline bool
-MemSpan::is_same(self_type const &that) const
+template <typename T>
+bool
+MemSpan<T>::is_same(self_type const &that) const
 {
-  return _data == that._data && _size == that._size;
+  return _ptr == that._ptr && _count == that._count;
 }
 
-inline bool
-MemSpan::operator==(self_type const &that) const
+template <typename T>
+bool
+MemSpan<T>::operator==(self_type const &that) const
 {
-  return _size == that._size && (_data == that._data || 0 == memcmp(this->data(), that.data(), _size));
+  return _count == that._count && (_ptr == that._ptr || 0 == memcmp(_ptr, that._ptr, this->size()));
 }
 
-inline bool
-MemSpan::operator!=(self_type const &that) const
+template <typename T>
+bool
+MemSpan<T>::operator!=(self_type const &that) const
 {
   return !(*this == that);
 }
 
-inline bool MemSpan::operator!() const
+template <typename T> bool MemSpan<T>::operator!() const
 {
-  return _size == 0;
+  return _count == 0;
 }
 
-inline MemSpan::operator bool() const
+template <typename T> MemSpan<T>::operator bool() const
 {
-  return _size != 0;
+  return _count != 0;
 }
 
-inline bool
-MemSpan::empty() const
+template <typename T>
+bool
+MemSpan<T>::empty() const
 {
-  return _size == 0;
+  return _count == 0;
 }
 
-inline MemSpan &
-MemSpan::operator++()
+template <typename T>
+T *
+MemSpan<T>::begin() const
 {
-  _data = static_cast<char *>(_data) + 1;
-  --_size;
-  return *this;
+  return _ptr;
 }
 
-inline MemSpan &
-MemSpan::operator+=(ptrdiff_t n)
+template <typename T>
+T *
+MemSpan<T>::data() const
 {
-  if (n > _size) {
-    this->clear();
-  } else {
-    _data = static_cast<char *>(_data) + n;
-    _size -= n;
-  }
-  return *this;
+  return _ptr;
 }
 
-inline char *
-MemSpan::begin()
+template <typename T>
+T *
+MemSpan<T>::end() const
 {
-  return static_cast<char *>(_data);
+  return _ptr + _count;
 }
 
-inline const char *
-MemSpan::begin() const
+template <typename T> T &MemSpan<T>::operator[](size_t idx) const
 {
-  return static_cast<const char *>(_data);
+  return _ptr[idx];
 }
 
-inline void *
-MemSpan::data()
+template <typename T>
+size_t
+MemSpan<T>::count() const
 {
-  return _data;
+  return _count;
 }
 
-inline const void *
-MemSpan::data() const
+template <typename T>
+size_t
+MemSpan<T>::size() const
 {
-  return _data;
+  return _count * sizeof(T);
 }
 
-inline char *
-MemSpan::end()
+template <typename T>
+bool
+MemSpan<T>::contains(T const *ptr) const
 {
-  return static_cast<char *>(_data) + _size;
+  return _ptr <= ptr && ptr < _ptr + _count;
 }
 
-inline const char *
-MemSpan::end() const
+template <typename T>
+auto
+MemSpan<T>::prefix(size_t count) const -> self_type
 {
-  return static_cast<const char *>(_data) + _size;
+  return {_ptr, std::min(count, _count)};
 }
 
-inline void *
-MemSpan::data_end()
+template <typename T>
+auto
+MemSpan<T>::remove_prefix(size_t count) -> self_type &
 {
-  return static_cast<char *>(_data) + _size;
+  count = std::min(_count, count);
+  _count -= count;
+  _ptr += count;
+  return *this;
 }
 
-inline const void *
-MemSpan::data_end() const
+template <typename T>
+auto
+MemSpan<T>::suffix(size_t count) const -> self_type
 {
-  return static_cast<char *>(_data) + _size;
+  count = std::min(_count, count);
+  return {(_ptr + _count) - count, count};
 }
 
-inline constexpr ptrdiff_t
-MemSpan::ssize() const
+template <typename T>
+MemSpan<T> &
+MemSpan<T>::remove_suffix(size_t count)
 {
-  return _size;
+  _count -= std::min(count, _count);
+  return *this;
 }
 
-inline size_t
-MemSpan::size() const
+template <typename T>
+template <typename U>
+MemSpan<U>
+MemSpan<T>::rebind() const
+{
+  static_assert(detail::is_span_compatible<T, U>::value,
+                "MemSpan only allows rebinding between types who sizes are integral multiples.");
+  return {static_cast<U *>(static_cast<void *>(_ptr)), detail::is_span_compatible<T, U>::count(this->size())};
+}
+
+template <typename T>
+std::string_view
+MemSpan<T>::view() const
+{
+  return {static_cast<const char *>(_ptr), this->size()};
+}
+
+// --- void specialization ---
+
+template <typename U> constexpr MemSpan<void>::MemSpan(MemSpan<U> const &that) : _ptr(that._ptr), _size(that.size()) {}
+
+inline constexpr MemSpan<void>::MemSpan(value_type *ptr, size_t n) : _ptr{ptr}, _size{n} {}
+
+inline MemSpan<void>::MemSpan(value_type *first, value_type *last) : _ptr{first}, _size{detail::ptr_distance(first, last)} {}
+
+inline constexpr MemSpan<void>::MemSpan(std::nullptr_t) {}
+
+inline MemSpan<void> &
+MemSpan<void>::assign(value_type *ptr, size_t n)
 {
-  return static_cast<size_t>(_size);
+  _ptr  = ptr;
+  _size = n;
+  return *this;
 }
 
-inline MemSpan &
-MemSpan::operator=(MemSpan const &that)
+inline MemSpan<void> &
+MemSpan<void>::assign(value_type *first, value_type const *last)
 {
-  _data = that._data;
-  _size = that._size;
+  _ptr  = first;
+  _size = detail::ptr_distance(first, last);
+  return *this;
+}
+
+inline MemSpan<void> &
+MemSpan<void>::clear()
+{
+  _ptr  = nullptr;
+  _size = 0;
   return *this;
 }
 
 inline bool
-MemSpan::contains(const void *p) const
+MemSpan<void>::is_same(self_type const &that) const
 {
-  return !this->empty() && _data <= p && p < this->data_end();
+  return _ptr == that._ptr && _size == that._size;
 }
 
-inline MemSpan
-MemSpan::prefix(const void *p) const
+inline bool
+MemSpan<void>::operator==(self_type const &that) const
 {
-  self_type zret;
-  if (_data <= p && p <= this->data_end())
-    zret.assign(_data, p);
-  return zret;
+  return _size == that._size && (_ptr == that._ptr || 0 == memcmp(_ptr, that._ptr, _size));
 }
 
-inline MemSpan
-MemSpan::prefix(ptrdiff_t n) const
+inline bool
+MemSpan<void>::operator!=(self_type const &that) const
 {
-  return {_data, std::min(n, _size)};
+  return !(*this == that);
 }
 
-inline MemSpan &
-MemSpan::remove_prefix(ptrdiff_t n)
+inline bool MemSpan<void>::operator!() const
 {
-  if (n < 0) {
-  } else if (n <= _size) {
-    _size -= n;
-    _data = static_cast<char *>(_data) + n;
-  } else {
-    this->clear();
-  }
-  return *this;
+  return _size == 0;
 }
 
-inline MemSpan
-MemSpan::suffix(void const *p) const
+inline MemSpan<void>::operator bool() const
 {
-  self_type zret;
-  if (_data <= p && p <= this->data_end()) {
-    zret.assign(const_cast<void *>(p), this->data_end());
-  }
-  return zret;
+  return _size != 0;
 }
 
-inline MemSpan
-MemSpan::suffix(ptrdiff_t n) const
+inline bool
+MemSpan<void>::empty() const
 {
-  self_type zret;
-  if (n < 0) {
-    n = std::max(ptrdiff_t{0}, n + _size);
-  }
-  if (n <= _size) {
-    zret.assign(static_cast<char *>(_data) + n, _size - n);
-  }
-  return zret;
+  return _size == 0;
 }
 
-inline MemSpan &
-MemSpan::remove_suffix(void const *p)
+inline void *
+MemSpan<void>::data() const
 {
-  if (_data <= p && p <= this->data_end()) {
-    _size -= distance(this->data_end(), p);
-  }
-  return *this;
+  return _ptr;
 }
 
-inline MemSpan &
-MemSpan::remove_suffix(ptrdiff_t n)
+inline void *
+MemSpan<void>::data_end() const
 {
-  if (n < 0) {
-    n = std::max(ptrdiff_t{0}, n + _size);
-  }
-  if (n <= _size) {
-    _size -= n;
-    _data = static_cast<char *>(_data) + n;
-  }
-  return *this;
+  return static_cast<char *>(_ptr) + _size;
 }
 
-template <typename V>
-inline V
-MemSpan::at(ptrdiff_t n) const
+inline size_t
+MemSpan<void>::size() const
 {
-  return static_cast<V *>(_data)[n];
+  return _size;
 }
 
-template <typename V>
-inline V const *
-MemSpan::ptr(ptrdiff_t n) const
+template <typename U>
+auto
+MemSpan<void>::operator=(MemSpan<U> const &that) -> self_type &
 {
-  return static_cast<V const *>(_data) + n;
+  _ptr  = that._ptr;
+  _size = that.size();
+  return *this;
 }
 
-template <typename V>
-inline V *
-MemSpan::find(V v) const
+inline bool
+MemSpan<void>::contains(value_type const *ptr) const
 {
-  for (V *spot = static_cast<V *>(_data), *limit = spot + (_size / sizeof(V)); spot < limit; ++spot)
-    if (v == *spot)
-      return spot;
-  return nullptr;
+  return _ptr <= ptr && ptr < this->data_end();
 }
 
-// Specialize char for performance.
-template <>
-inline char *
-MemSpan::find(char v) const
+inline MemSpan<void>
+MemSpan<void>::prefix(size_t n) const
 {
-  return static_cast<char *>(memchr(_data, v, _size));
+  return {_ptr, std::min(n, _size)};
 }
 
-template <typename V, typename F>
-inline V *
-MemSpan::find_if(F const &pred)
+inline MemSpan<void> &
+MemSpan<void>::remove_prefix(size_t n)
 {
-  for (V *p = static_cast<V *>(_data), *limit = p + (_size / sizeof(V)); p < limit; ++p)
-    if (pred(*p))
-      return p;
-  return nullptr;
+  n = std::max(_size, n);
+  _size -= n;
+  _ptr = static_cast<char *>(_ptr) + n;
+  return *this;
 }
 
-inline std::string_view
-MemSpan::view() const
+inline MemSpan<void>
+MemSpan<void>::suffix(size_t count) const
 {
-  return {static_cast<const char *>(_data), static_cast<size_t>(_size)};
+  count = std::max(count, _size);
+  return {static_cast<char *>(this->data_end()) - count, size_t(count)};
 }
 
-inline MemSpan::operator std::string_view() const
+inline MemSpan<void> &
+MemSpan<void>::remove_suffix(size_t count)
 {
-  return this->view();
+  _size -= std::max(count, _size);
+  return *this;
 }
 
-} // namespace ts
+template <typename U>
+MemSpan<U>
+MemSpan<void>::rebind() const
+{
+  return {static_cast<U *>(_ptr), detail::is_span_compatible<void, U>::count(_size)};
+}
 
-namespace std
+// Specialize so that @c void -> @c void rebinding compiles and works as expected.
+template <>
+inline MemSpan<void>
+MemSpan<void>::rebind() const
 {
-inline ostream &
-operator<<(ostream &os, const ts::MemSpan &b)
+  return *this;
+}
+
+inline std::string_view
+MemSpan<void>::view() const
 {
-  if (os.good()) {
-    os << b.size() << '@' << hex << b.data();
-  }
-  return os;
+  return {static_cast<char const *>(_ptr), _size};
 }
-} // namespace std
+
+} // namespace ts
diff --git a/lib/records/I_RecCore.h b/lib/records/I_RecCore.h
index 188b331..7609e32 100644
--- a/lib/records/I_RecCore.h
+++ b/lib/records/I_RecCore.h
@@ -307,5 +307,5 @@ RecErrT RecSetSyncRequired(char *name, bool lock = true);
 //------------------------------------------------------------------------
 // Manager Callback
 //------------------------------------------------------------------------
-using RecManagerCb = std::function<void(ts::MemSpan)>;
+using RecManagerCb = std::function<void(ts::MemSpan<void>)>;
 int RecRegisterManagerCb(int _signal, RecManagerCb const &_fn);
diff --git a/lib/records/P_RecMessage.h b/lib/records/P_RecMessage.h
index d41f74c..24930fe 100644
--- a/lib/records/P_RecMessage.h
+++ b/lib/records/P_RecMessage.h
@@ -45,7 +45,7 @@ int RecMessageUnmarshalNext(RecMessage *msg, RecMessageItr *itr, RecRecord **rec
 
 int RecMessageSend(RecMessage *msg);
 int RecMessageRegisterRecvCb(RecMessageRecvCb recv_cb, void *cookie);
-void RecMessageRecvThis(ts::MemSpan);
+void RecMessageRecvThis(ts::MemSpan<void>);
 
 RecMessage *RecMessageReadFromDisk(const char *fpath);
 int RecMessageWriteToDisk(RecMessage *msg, const char *fpath);
diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc
index 5f5ddb7..02c5d19 100644
--- a/lib/records/RecHttp.cc
+++ b/lib/records/RecHttp.cc
@@ -713,7 +713,7 @@ SessionProtocolNameRegistry::toIndex(ts::TextView name)
   if (INVALID == zret) {
     if (m_n < MAX) {
       // Localize the name by copying it in to the arena.
-      auto text = m_arena.alloc(name.size() + 1);
+      auto text = m_arena.alloc(name.size() + 1).rebind<char>();
       memcpy(text.data(), name.data(), name.size());
       text.end()[-1] = '\0';
       m_names[m_n]   = text.view();
diff --git a/lib/records/RecMessage.cc b/lib/records/RecMessage.cc
index c18d502..b895256 100644
--- a/lib/records/RecMessage.cc
+++ b/lib/records/RecMessage.cc
@@ -245,7 +245,7 @@ RecMessageRegisterRecvCb(RecMessageRecvCb recv_cb, void *cookie)
 //-------------------------------------------------------------------------
 
 void
-RecMessageRecvThis(ts::MemSpan span)
+RecMessageRecvThis(ts::MemSpan<void> span)
 {
   RecMessage *msg = static_cast<RecMessage *>(span.data());
   g_recv_cb(msg, msg->msg_type, g_recv_cookie);
diff --git a/mgmt/BaseManager.cc b/mgmt/BaseManager.cc
index 86ef100..0f3d8a1 100644
--- a/mgmt/BaseManager.cc
+++ b/mgmt/BaseManager.cc
@@ -73,7 +73,7 @@ BaseManager::registerMgmtCallback(int msg_id, MgmtCallback const &cb)
 }
 
 void
-BaseManager::executeMgmtCallback(int msg_id, ts::MemSpan span)
+BaseManager::executeMgmtCallback(int msg_id, ts::MemSpan<void> span)
 {
   if (auto it = mgmt_callback_table.find(msg_id); it != mgmt_callback_table.end()) {
     for (auto &&cb : it->second) {
diff --git a/mgmt/BaseManager.h b/mgmt/BaseManager.h
index 9f72212..291c0cd 100644
--- a/mgmt/BaseManager.h
+++ b/mgmt/BaseManager.h
@@ -94,10 +94,10 @@
 struct MgmtMessageHdr {
   int msg_id;
   int data_len;
-  ts::MemSpan
+  ts::MemSpan<void>
   payload()
   {
-    return {reinterpret_cast<char *>(this) + sizeof(*this), data_len};
+    return {this + 1, static_cast<size_t>(data_len)};
   }
 };
 
@@ -136,7 +136,7 @@ public:
   MgmtMessageHdr *dequeue();
 
 protected:
-  void executeMgmtCallback(int msg_id, ts::MemSpan span);
+  void executeMgmtCallback(int msg_id, ts::MemSpan<void> span);
 
   /// The mapping from an event type to a list of callbacks to invoke.
   std::unordered_map<int, MgmtCallbackList> mgmt_callback_table;
diff --git a/mgmt/LocalManager.cc b/mgmt/LocalManager.cc
index 15ec02e..3c33dc7 100644
--- a/mgmt/LocalManager.cc
+++ b/mgmt/LocalManager.cc
@@ -568,7 +568,7 @@ LocalManager::handleMgmtMsgFromProcesses(MgmtMessageHdr *mh)
   }
   case MGMT_SIGNAL_LIBRECORDS:
     if (mh->data_len > 0) {
-      executeMgmtCallback(MGMT_SIGNAL_LIBRECORDS, {data_raw, mh->data_len});
+      executeMgmtCallback(MGMT_SIGNAL_LIBRECORDS, {data_raw, static_cast<size_t>(mh->data_len)});
     } else {
       executeMgmtCallback(MGMT_SIGNAL_LIBRECORDS, {});
     }
@@ -782,7 +782,7 @@ LocalManager::processEventQueue()
     bool handled_by_mgmt = false;
 
     MgmtMessageHdr *mh = this->dequeue();
-    auto payload       = mh->payload();
+    auto payload       = mh->payload().rebind<char>();
 
     // check if we have a local file update
     if (mh->msg_id == MGMT_EVENT_CONFIG_FILE_UPDATE || mh->msg_id == MGMT_EVENT_CONFIG_FILE_UPDATE_NO_INC_VERSION) {
diff --git a/mgmt/MgmtDefs.h b/mgmt/MgmtDefs.h
index 87dadf6..6d1aad9 100644
--- a/mgmt/MgmtDefs.h
+++ b/mgmt/MgmtDefs.h
@@ -51,7 +51,7 @@ enum MgmtType {
 /// Management callback signature.
 /// The memory span is the message payload for the callback.
 /// This can be a lambda, which should be used if additional context information is needed.
-using MgmtCallback = std::function<void(ts::MemSpan)>;
+using MgmtCallback = std::function<void(ts::MemSpan<void>)>;
 
 //-------------------------------------------------------------------------
 // API conversion functions.
diff --git a/mgmt/ProcessManager.cc b/mgmt/ProcessManager.cc
index b492c31..4636429 100644
--- a/mgmt/ProcessManager.cc
+++ b/mgmt/ProcessManager.cc
@@ -464,11 +464,12 @@ ProcessManager::handleMgmtMsgFromLM(MgmtMessageHdr *mh)
   case MGMT_EVENT_ROLL_LOG_FILES:
     executeMgmtCallback(MGMT_EVENT_ROLL_LOG_FILES, {});
     break;
-  case MGMT_EVENT_PLUGIN_CONFIG_UPDATE:
-    if (!payload.empty() && payload.at<char>(0) != '\0' && this->cbtable) {
-      this->cbtable->invoke(static_cast<char const *>(payload.data()));
+  case MGMT_EVENT_PLUGIN_CONFIG_UPDATE: {
+    auto msg{payload.rebind<char>()};
+    if (!msg.empty() && msg[0] != '\0' && this->cbtable) {
+      this->cbtable->invoke(msg.data());
     }
-    break;
+  } break;
   case MGMT_EVENT_CONFIG_FILE_UPDATE:
   case MGMT_EVENT_CONFIG_FILE_UPDATE_NO_INC_VERSION:
     /*
diff --git a/proxy/logging/LogConfig.cc b/proxy/logging/LogConfig.cc
index 2644011..e61f1e0 100644
--- a/proxy/logging/LogConfig.cc
+++ b/proxy/logging/LogConfig.cc
@@ -96,7 +96,8 @@ LogConfig::setup_default_values()
   max_line_size     = 9216; // size of pipe buffer for SunOS 5.6
 }
 
-void LogConfig::reconfigure_mgmt_variables(ts::MemSpan)
+void
+LogConfig::reconfigure_mgmt_variables(ts::MemSpan<void>)
 {
   Note("received log reconfiguration event, rolling now");
   Log::config->roll_log_files_now = true;
diff --git a/proxy/logging/LogConfig.h b/proxy/logging/LogConfig.h
index c36d38b..b3372e3 100644
--- a/proxy/logging/LogConfig.h
+++ b/proxy/logging/LogConfig.h
@@ -204,7 +204,7 @@ public:
   void read_configuration_variables();
 
   // CVR This is the mgmt callback function, hence all the strange arguments
-  static void reconfigure_mgmt_variables(ts::MemSpan);
+  static void reconfigure_mgmt_variables(ts::MemSpan<void>);
 
   int
   get_max_space_mb() const
diff --git a/src/traffic_cache_tool/CacheDefs.cc b/src/traffic_cache_tool/CacheDefs.cc
index e84fd23..062cd42 100644
--- a/src/traffic_cache_tool/CacheDefs.cc
+++ b/src/traffic_cache_tool/CacheDefs.cc
@@ -203,7 +203,7 @@ Stripe::Chunk::~Chunk()
   this->clear();
 }
 void
-Stripe::Chunk::append(MemSpan m)
+Stripe::Chunk::append(MemSpan<void> m)
 {
   _chain.push_back(m);
 }
@@ -270,17 +270,17 @@ Stripe::validateMeta(StripeMeta const *meta)
 }
 
 bool
-Stripe::probeMeta(MemSpan &mem, StripeMeta const *base_meta)
+Stripe::probeMeta(MemSpan<void> &mem, StripeMeta const *base_meta)
 {
   while (mem.size() >= sizeof(StripeMeta)) {
-    StripeMeta const *meta = mem.ptr<StripeMeta>(0);
+    StripeMeta const *meta = static_cast<StripeMeta *>(mem.data());
     if (this->validateMeta(meta) && (base_meta == nullptr ||               // no base version to check against.
                                      (meta->version == base_meta->version) // need more checks here I think.
                                      )) {
       return true;
     }
     // The meta data is stored aligned on a stripe block boundary, so only need to check there.
-    mem += CacheStoreBlocks::SCALE;
+    mem.remove_prefix(CacheStoreBlocks::SCALE);
   }
   return false;
 }
@@ -884,7 +884,7 @@ Stripe::loadMeta()
   int fd = _span->_fd;
   Bytes n;
   bool found;
-  MemSpan data; // The current view of the read buffer.
+  MemSpan<void> data; // The current view of the read buffer.
   Bytes delta;
   Bytes pos = _start;
   // Avoid searching the entire span, because some of it must be content. Assume that AOS is more than 160
@@ -908,10 +908,10 @@ Stripe::loadMeta()
   ssize_t headerbyteCount = pread(fd, stripe_buff2, SBSIZE, pos);
   n.assign(headerbyteCount);
   data.assign(stripe_buff2, n);
-  meta = data.ptr<StripeMeta>(0);
+  meta = static_cast<StripeMeta *>(data.data());
   // TODO:: We need to read more data at this point  to populate dir
   if (this->validateMeta(meta)) {
-    delta              = Bytes(data.ptr<char>(0) - stripe_buff2);
+    delta              = Bytes(data.rebind<char>().data() - stripe_buff2);
     _meta[A][HEAD]     = *meta;
     _meta_pos[A][HEAD] = round_down(pos + Bytes(delta));
     pos += round_up(SBSIZE);
@@ -925,15 +925,15 @@ Stripe::loadMeta()
       data.assign(buff, n);
       found = this->probeMeta(data, &_meta[A][HEAD]);
       if (found) {
-        ptrdiff_t diff     = data.ptr<char>(0) - buff;
-        _meta[A][FOOT]     = data.template at<StripeMeta>(0);
+        ptrdiff_t diff     = data.rebind<char>().data() - buff;
+        _meta[A][FOOT]     = static_cast<StripeMeta *>(data.data())[0];
         _meta_pos[A][FOOT] = round_down(pos + Bytes(diff));
         // don't bother attaching block if the footer is at the start
         if (diff > 0) {
           _directory._clip = Bytes(N - diff);
           _directory.append({bulk_buff.release(), N});
         }
-        data += SBSIZE; // skip footer for checking on B copy.
+        data.remove_prefix(SBSIZE); // skip footer for checking on B copy.
         break;
       } else {
         _directory.append({bulk_buff.release(), N});
@@ -956,7 +956,7 @@ Stripe::loadMeta()
     //      data.assign(stripe_buff, n);
     //    }
     pos  = this->_start + Bytes(vol_dirlen());
-    meta = data.ptr<StripeMeta>(0);
+    meta = static_cast<StripeMeta *>(data.data());
     if (this->validateMeta(meta)) {
       _meta[B][HEAD]     = *meta;
       _meta_pos[B][HEAD] = round_down(pos);
@@ -965,7 +965,7 @@ Stripe::loadMeta()
       pos += delta;
       n = Bytes(pread(fd, stripe_buff, ts::CacheStoreBlocks::SCALE, pos));
       data.assign(stripe_buff, n);
-      meta = data.ptr<StripeMeta>(0);
+      meta = static_cast<StripeMeta *>(data.data());
       if (this->validateMeta(meta)) {
         _meta[B][FOOT]     = *meta;
         _meta_pos[B][FOOT] = round_down(pos);
@@ -988,7 +988,7 @@ Stripe::loadMeta()
 
   n.assign(headerbyteCount);
   data.assign(stripe_buff2, n);
-  meta = data.ptr<StripeMeta>(0);
+  meta = static_cast<StripeMeta *>(data.data());
   // copy freelist
   freelist = (uint16_t *)malloc(_segments * sizeof(uint16_t));
   for (int i = 0; i < _segments; i++) {
diff --git a/src/traffic_cache_tool/CacheDefs.h b/src/traffic_cache_tool/CacheDefs.h
index 8b42a92..8a1b202 100644
--- a/src/traffic_cache_tool/CacheDefs.h
+++ b/src/traffic_cache_tool/CacheDefs.h
@@ -482,12 +482,12 @@ struct Stripe {
     Bytes _skip;  ///< # of bytes not valid at the start of the first block.
     Bytes _clip;  ///< # of bytes not valid at the end of the last block.
 
-    typedef std::vector<MemSpan> Chain;
+    typedef std::vector<MemSpan<void>> Chain;
     Chain _chain; ///< Chain of blocks.
 
     ~Chunk();
 
-    void append(MemSpan m);
+    void append(MemSpan<void> m);
     void clear();
   };
 
@@ -505,7 +505,7 @@ struct Stripe {
 
       @return @c true if @a mem has valid data, @c false otherwise.
   */
-  bool probeMeta(MemSpan &mem, StripeMeta const *meta = nullptr);
+  bool probeMeta(MemSpan<void> &mem, StripeMeta const *meta = nullptr);
 
   /// Check a buffer for being valid stripe metadata.
   /// @return @c true if valid, @c false otherwise.
diff --git a/src/traffic_cache_tool/CacheScan.cc b/src/traffic_cache_tool/CacheScan.cc
index d46572b..893b613 100644
--- a/src/traffic_cache_tool/CacheScan.cc
+++ b/src/traffic_cache_tool/CacheScan.cc
@@ -344,7 +344,7 @@ CacheScan::unmarshal(char *buf, int len, RefCountObj *block_ref)
 
 // check if the url looks valid
 bool
-CacheScan::check_url(ts::MemSpan &mem, URLImpl *url)
+CacheScan::check_url(ts::MemSpan<char> &mem, URLImpl *url)
 {
   bool in_bound = false; // boolean to check if address in bound
   if (!url->m_ptr_scheme) {
@@ -364,7 +364,7 @@ CacheScan::get_alternates(const char *buf, int length, bool search)
 
   char *start            = (char *)buf;
   RefCountObj *block_ref = nullptr;
-  ts::MemSpan doc_mem((char *)buf, length);
+  ts::MemSpan<char> doc_mem((char *)buf, length);
 
   while (length - (buf - start) > (int)sizeof(HTTPCacheAlt)) {
     HTTPCacheAlt *a = (HTTPCacheAlt *)buf;
diff --git a/src/traffic_cache_tool/CacheScan.h b/src/traffic_cache_tool/CacheScan.h
index fc15447..c593b70 100644
--- a/src/traffic_cache_tool/CacheScan.h
+++ b/src/traffic_cache_tool/CacheScan.h
@@ -58,6 +58,6 @@ public:
   Errata unmarshal(URLImpl *obj, intptr_t offset);
   Errata unmarshal(MIMEFieldBlockImpl *mf, intptr_t offset);
   Errata unmarshal(MIMEHdrImpl *obj, intptr_t offset);
-  bool check_url(ts::MemSpan &mem, URLImpl *url);
+  bool check_url(ts::MemSpan<char> &mem, URLImpl *url);
 };
 } // namespace ct
diff --git a/src/traffic_server/HostStatus.cc b/src/traffic_server/HostStatus.cc
index ac208f7..1efecf7 100644
--- a/src/traffic_server/HostStatus.cc
+++ b/src/traffic_server/HostStatus.cc
@@ -32,7 +32,7 @@ getStatName(std::string &stat_name, const char *name)
 }
 
 static void
-mgmt_host_status_up_callback(ts::MemSpan span)
+mgmt_host_status_up_callback(ts::MemSpan<void> span)
 {
   MgmtInt op;
   MgmtMarshallString name;
@@ -65,7 +65,7 @@ mgmt_host_status_up_callback(ts::MemSpan span)
 }
 
 static void
-mgmt_host_status_down_callback(ts::MemSpan span)
+mgmt_host_status_down_callback(ts::MemSpan<void> span)
 {
   MgmtInt op;
   MgmtMarshallString name;
diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc
index 5674f70..3d6c630 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -115,10 +115,10 @@ extern "C" int plock(int);
 
 static const long MAX_LOGIN = ink_login_name_max();
 
-static void mgmt_restart_shutdown_callback(ts::MemSpan);
-static void mgmt_drain_callback(ts::MemSpan);
+static void mgmt_restart_shutdown_callback(ts::MemSpan<void>);
+static void mgmt_drain_callback(ts::MemSpan<void>);
 static void mgmt_storage_device_cmd_callback(int cmd, std::string_view const &arg);
-static void mgmt_lifecycle_msg_callback(ts::MemSpan);
+static void mgmt_lifecycle_msg_callback(ts::MemSpan<void>);
 static void init_ssl_ctx_callback(void *ctx, bool server);
 static void load_ssl_file_callback(const char *ssl_file, unsigned int options);
 static void load_remap_file_callback(const char *remap_file);
@@ -1971,8 +1971,8 @@ main(int /* argc ATS_UNUSED */, const char **argv)
     // Callback for various storage commands. These all go to the same function so we
     // pass the event code along so it can do the right thing. We cast that to <int> first
     // just to be safe because the value is a #define, not a typed value.
-    pmgmt->registerMgmtCallback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, [](ts::MemSpan span) -> void {
-      mgmt_storage_device_cmd_callback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, std::string_view{span});
+    pmgmt->registerMgmtCallback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, [](ts::MemSpan<void> span) -> void {
+      mgmt_storage_device_cmd_callback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, span.view());
     });
     pmgmt->registerMgmtCallback(MGMT_EVENT_LIFECYCLE_MESSAGE, &mgmt_lifecycle_msg_callback);
 
@@ -2019,15 +2019,16 @@ REGRESSION_TEST(Hdrs)(RegressionTest *t, int atype, int *pstatus)
 }
 #endif
 
-static void mgmt_restart_shutdown_callback(ts::MemSpan)
+static void
+mgmt_restart_shutdown_callback(ts::MemSpan<void>)
 {
   sync_cache_dir_on_shutdown();
 }
 
 static void
-mgmt_drain_callback(ts::MemSpan span)
+mgmt_drain_callback(ts::MemSpan<void> span)
 {
-  char *arg = static_cast<char *>(span.data());
+  char *arg = span.rebind<char>().data();
   TSSystemState::drain(span.size() == 2 && arg[0] == '1');
   RecSetRecordInt("proxy.node.config.draining", TSSystemState::is_draining() ? 1 : 0, REC_SOURCE_DEFAULT);
 }
@@ -2049,7 +2050,7 @@ mgmt_storage_device_cmd_callback(int cmd, std::string_view const &arg)
 }
 
 static void
-mgmt_lifecycle_msg_callback(ts::MemSpan span)
+mgmt_lifecycle_msg_callback(ts::MemSpan<void> span)
 {
   APIHook *hook = lifecycle_hooks->get(TS_LIFECYCLE_MSG_HOOK);
   TSPluginMsg msg;
diff --git a/src/tscore/BufferWriterFormat.cc b/src/tscore/BufferWriterFormat.cc
index 54eafdf..0060a2c 100644
--- a/src/tscore/BufferWriterFormat.cc
+++ b/src/tscore/BufferWriterFormat.cc
@@ -617,10 +617,10 @@ bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv)
 }
 
 BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span)
+bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan<void> const &span)
 {
   static const BWFormat default_fmt{"{:#x}@{:p}"};
-  if (spec._ext.size() && 'd' == spec._ext.front()) {
+  if ('x' == spec._type || 'X' == spec._type) {
     bwformat(w, spec, bwf::detail::MemDump(span.data(), span.size()));
   } else {
     w.print(default_fmt, span.size(), span.data());
diff --git a/src/tscore/MemArena.cc b/src/tscore/MemArena.cc
index 3dfcb0c..877cb97 100644
--- a/src/tscore/MemArena.cc
+++ b/src/tscore/MemArena.cc
@@ -66,10 +66,10 @@ MemArena::make_block(size_t n)
   return BlockPtr(new (::malloc(n)) Block(free_space));
 }
 
-MemSpan
+MemSpan<void>
 MemArena::alloc(size_t n)
 {
-  MemSpan zret;
+  MemSpan<void> zret;
   _active_allocated += n;
 
   if (!_active) {
diff --git a/src/tscore/unit_tests/test_BufferWriterFormat.cc b/src/tscore/unit_tests/test_BufferWriterFormat.cc
index d6f2858..a3ef870 100644
--- a/src/tscore/unit_tests/test_BufferWriterFormat.cc
+++ b/src/tscore/unit_tests/test_BufferWriterFormat.cc
@@ -185,10 +185,16 @@ TEST_CASE("BWFormat numerics", "[bwprint][bwformat]")
   REQUIRE(bw.view() == "0x200@0xbadd0956");
 
   bw.reduce(0);
-  bw.print("{::d}", ts::MemSpan(const_cast<char *>(char_ptr), 4));
+  bw.print("{:x}", ts::MemSpan(const_cast<char *>(char_ptr), 4));
   REQUIRE(bw.view() == "676f6f64");
   bw.reduce(0);
-  bw.print("{:#:d}", ts::MemSpan(const_cast<char *>(char_ptr), 4));
+  bw.print("{:#x}", ts::MemSpan(const_cast<char *>(char_ptr), 4));
+  REQUIRE(bw.view() == "0x676f6f64");
+  bw.reduce(0);
+  bw.print("{:x}", ts::MemSpan<void>(const_cast<char *>(char_ptr), 4));
+  REQUIRE(bw.view() == "676f6f64");
+  bw.reduce(0);
+  bw.print("{:#x}", ts::MemSpan<void>(const_cast<char *>(char_ptr), 4));
   REQUIRE(bw.view() == "0x676f6f64");
 
   std::string_view sv{"abc123"};
diff --git a/src/tscore/unit_tests/test_MemArena.cc b/src/tscore/unit_tests/test_MemArena.cc
index 3492c99..4084afe 100644
--- a/src/tscore/unit_tests/test_MemArena.cc
+++ b/src/tscore/unit_tests/test_MemArena.cc
@@ -38,10 +38,10 @@ TEST_CASE("MemArena generic", "[libts][MemArena]")
   REQUIRE(arena.size() == 0);
   REQUIRE(arena.reserved_size() >= 64);
 
-  ts::MemSpan span1 = arena.alloc(32);
+  auto span1 = arena.alloc(32);
   REQUIRE(span1.size() == 32);
 
-  ts::MemSpan span2 = arena.alloc(32);
+  auto span2 = arena.alloc(32);
   REQUIRE(span2.size() == 32);
 
   REQUIRE(span1.data() != span2.data());
@@ -55,7 +55,7 @@ TEST_CASE("MemArena generic", "[libts][MemArena]")
 TEST_CASE("MemArena freeze and thaw", "[libts][MemArena]")
 {
   MemArena arena;
-  MemSpan span1{arena.alloc(1024)};
+  auto span1{arena.alloc(1024)};
   REQUIRE(span1.size() == 1024);
   REQUIRE(arena.size() == 1024);
   REQUIRE(arena.reserved_size() >= 1024);
@@ -129,7 +129,7 @@ TEST_CASE("MemArena helper", "[libts][MemArena]")
 
   ts::MemArena arena{256};
   REQUIRE(arena.size() == 0);
-  ts::MemSpan s = arena.alloc(56);
+  ts::MemSpan<char> s = arena.alloc(56).rebind<char>();
   REQUIRE(arena.size() == 56);
   void *ptr = s.begin();
 
@@ -141,8 +141,8 @@ TEST_CASE("MemArena helper", "[libts][MemArena]")
   arena.freeze(128);
   REQUIRE(arena.contains((char *)ptr));
   REQUIRE(arena.contains((char *)ptr + 100));
-  ts::MemSpan s2 = arena.alloc(10);
-  void *ptr2     = s2.begin();
+  ts::MemSpan<char> s2 = arena.alloc(10).rebind<char>();
+  void *ptr2           = s2.begin();
   REQUIRE(arena.contains((char *)ptr));
   REQUIRE(arena.contains((char *)ptr2));
   REQUIRE(arena.allocated_size() == 56 + 10);
@@ -183,7 +183,7 @@ TEST_CASE("MemArena large alloc", "[libts][MemArena]")
   ts::MemSpan s = arena.alloc(4000);
   REQUIRE(s.size() == 4000);
 
-  ts::MemSpan s_a[10];
+  ts::MemSpan<void> s_a[10];
   s_a[0] = arena.alloc(100);
   s_a[1] = arena.alloc(200);
   s_a[2] = arena.alloc(300);
@@ -207,9 +207,9 @@ TEST_CASE("MemArena large alloc", "[libts][MemArena]")
 TEST_CASE("MemArena block allocation", "[libts][MemArena]")
 {
   ts::MemArena arena{64};
-  ts::MemSpan s  = arena.alloc(32);
-  ts::MemSpan s2 = arena.alloc(16);
-  ts::MemSpan s3 = arena.alloc(16);
+  ts::MemSpan<char> s  = arena.alloc(32).rebind<char>();
+  ts::MemSpan<char> s2 = arena.alloc(16).rebind<char>();
+  ts::MemSpan<char> s3 = arena.alloc(16).rebind<char>();
 
   REQUIRE(s.size() == 32);
   REQUIRE(arena.allocated_size() == 64);
@@ -233,9 +233,9 @@ TEST_CASE("MemArena full blocks", "[libts][MemArena]")
   size_t init_size = 32000;
   ts::MemArena arena(init_size);
 
-  MemSpan m1{arena.alloc(init_size - 64)};
-  MemSpan m2{arena.alloc(32000)};
-  MemSpan m3{arena.alloc(64000)};
+  MemSpan<char> m1{arena.alloc(init_size - 64).rebind<char>()};
+  MemSpan<char> m2{arena.alloc(32000).rebind<char>()};
+  MemSpan<char> m3{arena.alloc(64000).rebind<char>()};
 
   REQUIRE(arena.remaining() >= 64);
   REQUIRE(arena.reserved_size() > 32000 + 64000 + init_size);
diff --git a/src/tscpp/util/unit_tests/test_MemSpan.cc b/src/tscpp/util/unit_tests/test_MemSpan.cc
index 2149c5b..7149e68 100644
--- a/src/tscpp/util/unit_tests/test_MemSpan.cc
+++ b/src/tscpp/util/unit_tests/test_MemSpan.cc
@@ -1,23 +1,20 @@
 /** @file
 
-    TextView unit tests.
+    MemSpan unit tests.
 
     @section license License
 
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
+    Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+    agreements.  See the NOTICE file distributed with this work for additional information regarding
+    copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with the License.  You may
+    obtain a copy of the License at
 
     http://www.apache.org/licenses/LICENSE-2.0
 
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
+    Unless required by applicable law or agreed to in writing, software distributed under the
+    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+    express or implied. See the License for the specific language governing permissions and
     limitations under the License.
 */
 
@@ -27,30 +24,68 @@
 
 using ts::MemSpan;
 
-TEST_CASE("MemSpan", "[libts][MemSpan]")
+TEST_CASE("MemSpan", "[libswoc][MemSpan]")
 {
-  int idx[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+  int32_t idx[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
   char buff[1024];
-  MemSpan span(buff, sizeof(buff));
-  MemSpan left = span.prefix(512);
+
+  MemSpan<char> span(buff, sizeof(buff));
+  MemSpan<char> left = span.prefix(512);
   REQUIRE(left.size() == 512);
   REQUIRE(span.size() == 1024);
   span.remove_prefix(512);
   REQUIRE(span.size() == 512);
-  REQUIRE(left.data_end() == span.data());
+  REQUIRE(left.end() == span.begin());
+
+  left.assign(buff, sizeof(buff));
+  span = left.suffix(768);
+  left.remove_suffix(768);
+  REQUIRE(left.end() == span.begin());
+  REQUIRE(left.size() + span.size() == 1024);
 
-  MemSpan idx_span(idx);
+  MemSpan<int32_t> idx_span(idx);
+  REQUIRE(idx_span.count() == 11);
   REQUIRE(idx_span.size() == sizeof(idx));
   REQUIRE(idx_span.data() == idx);
-  REQUIRE(idx_span.find<int>(4) == idx + 4);
-  REQUIRE(idx_span.find<int>(8) == idx + 8);
-  MemSpan a = idx_span.suffix(idx_span.find<int>(7));
-  REQUIRE(a.at<int>(0) == 7);
-  MemSpan b = idx_span.suffix(-(4 * sizeof(int)));
-  REQUIRE(b.size() == 4 * sizeof(int));
-  REQUIRE(b.at<int>(0) == 7);
-  REQUIRE(a == b);
-  MemSpan c = idx_span.prefix(3 * sizeof(int));
-  REQUIRE(c.size() == 3 * sizeof(int));
-  REQUIRE(c.ptr<int>(2) == idx + 2);
-}
+
+  auto sp2 = idx_span.rebind<int16_t>();
+  REQUIRE(sp2.size() == idx_span.size());
+  REQUIRE(sp2.count() == 2 * idx_span.count());
+  REQUIRE(sp2[0] == 0);
+  REQUIRE(sp2[1] == 0);
+  // exactly one of { le, be } must be true.
+  bool le = sp2[2] == 1 && sp2[3] == 0;
+  bool be = sp2[2] == 0 && sp2[3] == 1;
+  REQUIRE(le != be);
+  auto idx2 = sp2.rebind<int32_t>(); // still the same if converted back to original?
+  REQUIRE(idx_span.is_same(idx2));
+
+  // Verify attempts to rebind on non-integral sized arrays fails.
+  span.assign(buff, 1022);
+  REQUIRE(span.size() == 1022);
+  REQUIRE(span.count() == 1022);
+  auto vs = span.rebind<void>();
+  REQUIRE_THROWS_AS(span.rebind<uint32_t>(), std::invalid_argument);
+  REQUIRE_THROWS_AS(vs.rebind<uint32_t>(), std::invalid_argument);
+
+  // Check for defaulting to a void rebind.
+  vs = span.rebind();
+  REQUIRE(vs.size() == 1022);
+
+  // Check for assignment to void.
+  vs = span;
+  REQUIRE(vs.size() == 1022);
+
+  // Test array constructors.
+  MemSpan<char> a{buff};
+  REQUIRE(a.size() == sizeof(buff));
+  REQUIRE(a.data() == buff);
+  float floats[] = {1.1, 2.2, 3.3, 4.4, 5.5};
+  MemSpan<float> fspan{floats};
+  REQUIRE(fspan.count() == 5);
+  REQUIRE(fspan[3] == 4.4f);
+  MemSpan<float> f2span{floats, floats + 5};
+  REQUIRE(fspan.data() == f2span.data());
+  REQUIRE(fspan.count() == f2span.count());
+  REQUIRE(fspan.is_same(f2span));
+};


Mime
View raw message