diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3ecfd5a9..204a0707 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -434,6 +434,7 @@ if(WITH_TESTS)
metadata_requirements_test
options_test
packed_int_vector_test
+ packed_ptr_test
pcm_sample_transformer_test
pcmaudio_categorizer_test
speedometer_test
diff --git a/include/dwarfs/internal/packed_ptr.h b/include/dwarfs/internal/packed_ptr.h
new file mode 100644
index 00000000..632ccec0
--- /dev/null
+++ b/include/dwarfs/internal/packed_ptr.h
@@ -0,0 +1,88 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/**
+ * \author Marcus Holland-Moritz (github@mhxnet.de)
+ * \copyright Copyright (c) Marcus Holland-Moritz
+ *
+ * This file is part of dwarfs.
+ *
+ * dwarfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dwarfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with dwarfs. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace dwarfs::internal {
+
+namespace detail {
+
+template
+struct underlying_type {
+ using type = T;
+};
+
+// specialization for enums
+template
+ requires std::is_enum_v
+struct underlying_type {
+ using type = std::underlying_type_t;
+};
+
+} // namespace detail
+
+template
+class packed_ptr {
+ public:
+ using data_type = DataType;
+ static constexpr size_t data_bits = DataBits;
+ static constexpr size_t data_mask = (1 << data_bits) - 1;
+
+ static_assert(
+ std::unsigned_integral::type>,
+ "data_type must be an unsigned integer type");
+
+ explicit packed_ptr(T* p = nullptr, data_type data = {})
+ : p_(build_packed_ptr(p, data)) {}
+
+ void set(T* p) { p_ = build_packed_ptr(p, get_data()); }
+ T* get() const { return reinterpret_cast(p_ & ~data_mask); }
+
+ T* operator->() const { return get(); }
+ T& operator*() const { return *get(); }
+ T& operator[](std::ptrdiff_t i) const { return get()[i]; }
+
+ data_type get_data() const { return static_cast(p_ & data_mask); }
+ void set_data(data_type data) { p_ = build_packed_ptr(get(), data); }
+
+ private:
+ static uintptr_t build_packed_ptr(T* p, data_type data) {
+ auto value = reinterpret_cast(p);
+ auto data_value = static_cast(data);
+ if (value & data_mask) {
+ throw std::invalid_argument("pointer is not aligned");
+ }
+ if (data_value & ~data_mask) {
+ throw std::invalid_argument("data out of bounds");
+ }
+ return value | data_value;
+ }
+
+ uintptr_t p_;
+};
+
+} // namespace dwarfs::internal
diff --git a/test/packed_ptr_test.cpp b/test/packed_ptr_test.cpp
new file mode 100644
index 00000000..6eca7084
--- /dev/null
+++ b/test/packed_ptr_test.cpp
@@ -0,0 +1,132 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/**
+ * \author Marcus Holland-Moritz (github@mhxnet.de)
+ * \copyright Copyright (c) Marcus Holland-Moritz
+ *
+ * This file is part of dwarfs.
+ *
+ * dwarfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dwarfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with dwarfs. If not, see .
+ */
+
+#include
+#include
+
+#include
+
+using namespace dwarfs::internal;
+
+TEST(packed_ptr, initialize) {
+ {
+ packed_ptr pp;
+
+ EXPECT_EQ(pp.get(), nullptr);
+ EXPECT_EQ(pp.get_data(), 0);
+ }
+
+ {
+ alignas(8) uint32_t i = 42;
+ packed_ptr pp(&i);
+
+ EXPECT_EQ(pp.get(), &i);
+ EXPECT_EQ(pp.get_data(), 0);
+ }
+
+ {
+ packed_ptr pp(nullptr, 0x7);
+
+ EXPECT_EQ(pp.get(), nullptr);
+ EXPECT_EQ(pp.get_data(), 0x7);
+ }
+
+ {
+ alignas(8) uint32_t i = 42;
+ packed_ptr pp(&i, 0x7);
+
+ EXPECT_EQ(pp.get(), &i);
+ EXPECT_EQ(pp.get_data(), 0x7);
+ EXPECT_EQ(*pp, 42);
+ }
+
+ EXPECT_THAT(
+ [] { packed_ptr pp(nullptr, 0x8); },
+ testing::ThrowsMessage("data out of bounds"));
+
+ EXPECT_THAT(
+ [] {
+ packed_ptr pp(reinterpret_cast(0x100004), 0x7);
+ },
+ testing::ThrowsMessage("pointer is not aligned"));
+}
+
+TEST(packed_ptr, integral) {
+ using ptr_type = std::pair;
+ packed_ptr pp;
+
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_vfirst), int>);
+ static_assert(std::is_same_vsecond), float>);
+
+ alignas(4) ptr_type p = {42, 2.0f};
+ pp.set(&p);
+
+ EXPECT_EQ(pp.get(), &p);
+ EXPECT_EQ(pp.get_data(), 0);
+ EXPECT_EQ(pp->first, 42);
+ EXPECT_EQ(pp->second, 2.0f);
+ EXPECT_EQ(pp[0].first, 42);
+ EXPECT_EQ(pp[0].second, 2.0f);
+
+ EXPECT_THAT(
+ [&] { pp.set_data(0x4); },
+ testing::ThrowsMessage("data out of bounds"));
+
+ pp.set_data(0x3);
+
+ EXPECT_EQ(pp.get(), &p);
+ EXPECT_EQ(pp.get_data(), 0x3);
+
+ EXPECT_THAT(
+ [&] { pp.set(reinterpret_cast(0x100001)); },
+ testing::ThrowsMessage("pointer is not aligned"));
+}
+
+TEST(packed_ptr, enumeration) {
+ using ptr_type = std::pair;
+ enum class test_enum : unsigned { A = 1, B, C, D };
+ packed_ptr pp;
+
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_v);
+ static_assert(std::is_same_vfirst), int>);
+
+ alignas(4) ptr_type p = {42, 2.0f};
+ pp.set(&p);
+
+ EXPECT_EQ(pp.get(), &p);
+ EXPECT_EQ(pp.get_data(), static_cast(0));
+
+ pp.set_data(test_enum::B);
+
+ EXPECT_EQ(pp.get(), &p);
+ EXPECT_EQ(pp.get_data(), test_enum::B);
+
+ EXPECT_THAT(
+ [&] { pp.set_data(test_enum::D); },
+ testing::ThrowsMessage("data out of bounds"));
+}