raw: Add the ability to extract references to data in buffers

Change-Id: If3de6cfbc63e4a826f60f9f56c2e1ad533e62441
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/stdplus/raw.hpp b/src/stdplus/raw.hpp
index aaf803e..f6938e2 100644
--- a/src/stdplus/raw.hpp
+++ b/src/stdplus/raw.hpp
@@ -69,6 +69,27 @@
     return ret;
 }
 
+/** @brief References the data from a buffer if aligned
+ *
+ *  @param[in] data - The data buffer being referenced
+ *  @return The reference to the data in the new type
+ */
+template <typename T, typename Container,
+          typename Tp = detail::copyConst<T, detail::dataType<Container>>>
+Tp& refFrom(Container& c)
+{
+    static_assert(std::is_trivially_copyable_v<Tp>);
+    static_assert(detail::trivialContainer<Container>);
+    static_assert(sizeof(*std::data(c)) % alignof(Tp) == 0);
+    const size_t bytes = std::size(c) * sizeof(*std::data(c));
+    if (bytes < sizeof(Tp))
+    {
+        throw std::runtime_error(
+            fmt::format("RefFrom: {} < {}", bytes, sizeof(Tp)));
+    }
+    return *reinterpret_cast<Tp*>(std::data(c));
+}
+
 /** @brief Extracts data from a buffer into a copyable type
  *         Updates the data buffer to show that data was removed
  *
@@ -95,6 +116,33 @@
 }
 #endif
 
+/** @brief Extracts data from a buffer as a reference if aligned
+ *         Updates the data buffer to show that data was removed
+ *
+ *  @param[in,out] data - The data buffer being extracted from
+ *  @return A reference to the data
+ */
+template <typename T, typename CharT>
+const T& extractRef(std::basic_string_view<CharT>& data)
+{
+    const T& ret = refFrom<T>(data);
+    static_assert(sizeof(T) % sizeof(CharT) == 0);
+    data.remove_prefix(sizeof(T) / sizeof(CharT));
+    return ret;
+}
+#ifdef STDPLUS_SPAN_TYPE
+template <typename T, typename IntT,
+          typename = std::enable_if_t<std::is_trivially_copyable_v<IntT>>,
+          typename Tp = detail::copyConst<T, IntT>>
+Tp& extractRef(span<IntT>& data)
+{
+    Tp& ret = refFrom<Tp>(data);
+    static_assert(sizeof(Tp) % sizeof(IntT) == 0);
+    data = data.subspan(sizeof(Tp) / sizeof(IntT));
+    return ret;
+}
+#endif
+
 /** @brief Returns the span referencing the data of the raw trivial type
  *         or of trivial types in a contiguous container.
  *
diff --git a/test/raw.cpp b/test/raw.cpp
index fd8ae71..8be8b7b 100644
--- a/test/raw.cpp
+++ b/test/raw.cpp
@@ -23,9 +23,10 @@
 
 TEST_CASE("Copy From Empty", "[CopyFrom]")
 {
-    const std::string_view s;
+    const std::string_view cs;
+    CHECK_THROWS_AS(copyFrom<int>(cs), std::runtime_error);
+    std::string_view s;
     CHECK_THROWS_AS(copyFrom<int>(s), std::runtime_error);
-    CHECK(s.empty());
 }
 
 TEST_CASE("Copy From Basic", "[CopyFrom]")
@@ -43,6 +44,39 @@
     CHECK('d' == copyFrom<char>(s2));
 }
 
+struct Int
+{
+    uint8_t data[sizeof(int)];
+
+    inline bool operator==(const Int& other) const
+    {
+        return memcmp(data, other.data, sizeof(data)) == 0;
+    }
+};
+
+TEST_CASE("Ref From Empty", "[RefFrom]")
+{
+    const std::string_view cs;
+    CHECK_THROWS_AS(refFrom<Int>(cs), std::runtime_error);
+    std::string_view s;
+    CHECK_THROWS_AS(refFrom<Int>(s), std::runtime_error);
+}
+
+TEST_CASE("Ref From Basic", "[RefFrom]")
+{
+    Int a = {4, 0, 0, 4};
+    const std::string_view s(reinterpret_cast<char*>(&a), sizeof(a));
+    CHECK(a == refFrom<Int>(s));
+}
+
+TEST_CASE("Ref From Partial", "[RefFrom]")
+{
+    const std::vector<char> s = {'a', 'b', 'c'};
+    CHECK('a' == refFrom<char>(s));
+    const char s2[] = "def";
+    CHECK('d' == refFrom<char>(s2));
+}
+
 TEST_CASE("Extract Too Small", "[Extract]")
 {
     std::string_view s("a");
@@ -65,6 +99,28 @@
     CHECK(2 == s.size());
 }
 
+TEST_CASE("Extract Ref Too Small", "[ExtractRef]")
+{
+    std::string_view s("a");
+    CHECK_THROWS_AS(extractRef<Int>(s), std::runtime_error);
+    CHECK(1 == s.size());
+}
+
+TEST_CASE("Extract Ref Basic", "[ExtractRef]")
+{
+    Int a = {4, 0, 0, 4};
+    std::string_view s(reinterpret_cast<char*>(&a), sizeof(a));
+    CHECK(a == extractRef<Int>(s));
+    CHECK(s.empty());
+}
+
+TEST_CASE("Extract Ref Partial", "[ExtractRef]")
+{
+    std::string_view s("abc");
+    CHECK('a' == extractRef<char>(s));
+    CHECK(2 == s.size());
+}
+
 TEST_CASE("As View Byte", "[AsView]")
 {
     int32_t a = 4;
@@ -115,6 +171,30 @@
     CHECK(v.size() - 1 == s.size());
 }
 
+TEST_CASE("Span Extract Ref TooSmall", "[ExtractRef]")
+{
+    const std::vector<char> v = {'c'};
+    span<const char> s = v;
+    CHECK_THROWS_AS(extractRef<Int>(s), std::runtime_error);
+    CHECK(1 == s.size());
+}
+
+TEST_CASE("Span Extract Ref Basic", "[ExtractRef]")
+{
+    const std::vector<Int> v = {{4, 0, 0, 4}};
+    span<const Int> s = v;
+    CHECK(v[0] == extractRef<Int>(s));
+    CHECK(s.empty());
+}
+
+TEST_CASE("Span Extract Ref Larger", "[ExtractRef]")
+{
+    const std::vector<Int> v{{3}, {4}, {5}};
+    span<const Int> s = v;
+    CHECK(v[0] == extractRef<Int>(s));
+    CHECK(v.size() - 1 == s.size());
+}
+
 TEST_CASE("As Span const", "[AsSpan]")
 {
     const uint64_t data = htole64(0xffff0000);