numeric/endian: Add packed integer endian types

This makes it trivial to write packed structs for wire level
serialization.

Change-Id: I117eb6a80494c3ddca2a6924fd76be7df7365228
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/numeric/endian.hpp b/include/stdplus/numeric/endian.hpp
index 39487ac..0dcdfc6 100644
--- a/include/stdplus/numeric/endian.hpp
+++ b/include/stdplus/numeric/endian.hpp
@@ -167,4 +167,67 @@
     return etoh<std::endian::big>(t);
 }
 
+template <typename T, std::endian E>
+struct EndianPacked
+{
+    using value_type = T;
+
+    constexpr EndianPacked() noexcept = default;
+    constexpr EndianPacked(T t) noexcept
+    {
+        *this = t;
+    }
+    constexpr EndianPacked& operator=(T t) noexcept
+    {
+        data.n = htoe<E>(t);
+        return *this;
+    }
+
+    constexpr T value() const noexcept
+    {
+        return etoh<E>(data.n);
+    }
+
+    constexpr operator T() const noexcept
+    {
+        return value();
+    }
+
+    struct
+    {
+        T n;
+    } __attribute__((packed)) data;
+};
+
+using int8_ubt = EndianPacked<std::int8_t, std::endian::big>;
+using int16_ubt = EndianPacked<std::int16_t, std::endian::big>;
+using int32_ubt = EndianPacked<std::int32_t, std::endian::big>;
+using int64_ubt = EndianPacked<std::int64_t, std::endian::big>;
+
+using int8_ult = EndianPacked<std::int8_t, std::endian::little>;
+using int16_ult = EndianPacked<std::int16_t, std::endian::little>;
+using int32_ult = EndianPacked<std::int32_t, std::endian::little>;
+using int64_ult = EndianPacked<std::int64_t, std::endian::little>;
+
+using int8_unt = EndianPacked<std::int8_t, std::endian::big>;
+using int16_unt = EndianPacked<std::int16_t, std::endian::big>;
+using int32_unt = EndianPacked<std::int32_t, std::endian::big>;
+using int64_unt = EndianPacked<std::int64_t, std::endian::big>;
+
+using uint8_ubt = EndianPacked<std::uint8_t, std::endian::big>;
+using uint16_ubt = EndianPacked<std::uint16_t, std::endian::big>;
+using uint32_ubt = EndianPacked<std::uint32_t, std::endian::big>;
+using uint64_ubt = EndianPacked<std::uint64_t, std::endian::big>;
+
+using uint8_ult = EndianPacked<std::uint8_t, std::endian::little>;
+using uint16_ult = EndianPacked<std::uint16_t, std::endian::little>;
+using uint32_ult = EndianPacked<std::uint32_t, std::endian::little>;
+using uint64_ult = EndianPacked<std::uint64_t, std::endian::little>;
+
+using uint8_unt = EndianPacked<std::uint8_t, std::endian::big>;
+using uint16_unt = EndianPacked<std::uint16_t, std::endian::big>;
+using uint32_unt = EndianPacked<std::uint32_t, std::endian::big>;
+static_assert(alignof(uint32_unt) == 1);
+using uint64_unt = EndianPacked<std::uint64_t, std::endian::big>;
+
 } // namespace stdplus
diff --git a/test/numeric/endian.cpp b/test/numeric/endian.cpp
index d8f2ae8..8160753 100644
--- a/test/numeric/endian.cpp
+++ b/test/numeric/endian.cpp
@@ -1,6 +1,7 @@
 #include <stdplus/numeric/endian.hpp>
 
 #include <array>
+#include <cstring>
 
 #include <gtest/gtest.h>
 
@@ -21,4 +22,36 @@
     EXPECT_EQ(40, ntoh(hton(40)));
 }
 
+TEST(EndianPacked, Uint8)
+{
+    uint8_unt n = 0;
+    EXPECT_EQ(n.value(), 0);
+    EXPECT_EQ(n, 0);
+
+    n = 15;
+    EXPECT_EQ(n.value(), 15);
+    EXPECT_EQ(n, 15);
+}
+
+TEST(EndianPacked, Uint32)
+{
+    uint32_unt n = 0;
+    EXPECT_EQ(n.value(), 0);
+    EXPECT_EQ(n, 0);
+
+    n = 15;
+    EXPECT_EQ(n.value(), 15);
+    EXPECT_EQ(n, 15);
+}
+
+TEST(EndianPacked, ValidateUnderlying)
+{
+    uint32_ubt b(15);
+    uint32_ult l = {};
+    EXPECT_EQ(b, 15);
+    EXPECT_EQ(l, 0);
+    std::memcpy(&l, &b, sizeof(l));
+    EXPECT_EQ(0x0f000000, l);
+}
+
 } // namespace stdplus