simplify and unify serialization and deserialization
diff --git a/common/include/common_defs.hpp b/common/include/common_defs.hpp
index b4dceea..4a66657 100644
--- a/common/include/common_defs.hpp
+++ b/common/include/common_defs.hpp
@@ -56,7 +56,7 @@
 }
 
 template<typename T>
-static inline void write(std::ostream& os, T value) {
+static inline void write(std::ostream& os, T& value) {
   os.write(reinterpret_cast<const char*>(&value), sizeof(T));
 }
 
diff --git a/cpc/include/cpc_sketch_impl.hpp b/cpc/include/cpc_sketch_impl.hpp
index a314de8..e657b64 100644
--- a/cpc/include/cpc_sketch_impl.hpp
+++ b/cpc/include/cpc_sketch_impl.hpp
@@ -415,44 +415,44 @@
   const bool has_table = compressed.table_data.size() > 0;
   const bool has_window = compressed.window_data.size() > 0;
   const uint8_t preamble_ints = get_preamble_ints(num_coupons, has_hip, has_table, has_window);
-  os.write(reinterpret_cast<const char*>(&preamble_ints), sizeof(preamble_ints));
+  write(os, preamble_ints);
   const uint8_t serial_version = SERIAL_VERSION;
-  os.write(reinterpret_cast<const char*>(&serial_version), sizeof(serial_version));
+  write(os, serial_version);
   const uint8_t family = FAMILY;
-  os.write(reinterpret_cast<const char*>(&family), sizeof(family));
-  os.write(reinterpret_cast<const char*>(&lg_k), sizeof(lg_k));
-  os.write(reinterpret_cast<const char*>(&first_interesting_column), sizeof(first_interesting_column));
+  write(os, family);
+  write(os, lg_k);
+  write(os, first_interesting_column);
   const uint8_t flags_byte(
     (1 << flags::IS_COMPRESSED)
     | (has_hip ? 1 << flags::HAS_HIP : 0)
     | (has_table ? 1 << flags::HAS_TABLE : 0)
     | (has_window ? 1 << flags::HAS_WINDOW : 0)
   );
-  os.write(reinterpret_cast<const char*>(&flags_byte), sizeof(flags_byte));
+  write(os, flags_byte);
   const uint16_t seed_hash(compute_seed_hash(seed));
-  os.write((char*)&seed_hash, sizeof(seed_hash));
+  write(os, seed_hash);
   if (!is_empty()) {
-    os.write((char*)&num_coupons, sizeof(num_coupons));
+    write(os, num_coupons);
     if (has_table && has_window) {
       // if there is no window it is the same as number of coupons
-      os.write((char*)&compressed.table_num_entries, sizeof(compressed.table_num_entries));
+      write(os, compressed.table_num_entries);
       // HIP values can be in two different places in the sequence of fields
       // this is the first HIP decision point
       if (has_hip) write_hip(os);
     }
     if (has_table) {
-      os.write((char*)&compressed.table_data_words, sizeof(compressed.table_data_words));
+      write(os, compressed.table_data_words);
     }
     if (has_window) {
-      os.write((char*)&compressed.window_data_words, sizeof(compressed.window_data_words));
+      write(os, compressed.window_data_words);
     }
     // this is the second HIP decision point
     if (has_hip && !(has_table && has_window)) write_hip(os);
     if (has_window) {
-      os.write((char*)compressed.window_data.data(), compressed.window_data_words * sizeof(uint32_t));
+      os.write(reinterpret_cast<const char*>(compressed.window_data.data()), compressed.window_data_words * sizeof(uint32_t));
     }
     if (has_table) {
-      os.write((char*)compressed.table_data.data(), compressed.table_data_words * sizeof(uint32_t));
+      os.write(reinterpret_cast<const char*>(compressed.table_data.data()), compressed.table_data_words * sizeof(uint32_t));
     }
   }
 }
@@ -471,36 +471,36 @@
   const size_t size = header_size_bytes + (preamble_ints + compressed.table_data_words + compressed.window_data_words) * sizeof(uint32_t);
   vector_u8<A> bytes(size, 0, sliding_window.get_allocator());
   uint8_t* ptr = bytes.data() + header_size_bytes;
-  ptr += copy_to_mem(&preamble_ints, ptr, sizeof(preamble_ints));
+  ptr += copy_to_mem(preamble_ints, ptr);
   const uint8_t serial_version = SERIAL_VERSION;
-  ptr += copy_to_mem(&serial_version, ptr, sizeof(serial_version));
+  ptr += copy_to_mem(serial_version, ptr);
   const uint8_t family = FAMILY;
-  ptr += copy_to_mem(&family, ptr, sizeof(family));
-  ptr += copy_to_mem(&lg_k, ptr, sizeof(lg_k));
-  ptr += copy_to_mem(&first_interesting_column, ptr, sizeof(first_interesting_column));
+  ptr += copy_to_mem(family, ptr);
+  ptr += copy_to_mem(lg_k, ptr);
+  ptr += copy_to_mem(first_interesting_column, ptr);
   const uint8_t flags_byte(
     (1 << flags::IS_COMPRESSED)
     | (has_hip ? 1 << flags::HAS_HIP : 0)
     | (has_table ? 1 << flags::HAS_TABLE : 0)
     | (has_window ? 1 << flags::HAS_WINDOW : 0)
   );
-  ptr += copy_to_mem(&flags_byte, ptr, sizeof(flags_byte));
+  ptr += copy_to_mem(flags_byte, ptr);
   const uint16_t seed_hash = compute_seed_hash(seed);
-  ptr += copy_to_mem(&seed_hash, ptr, sizeof(seed_hash));
+  ptr += copy_to_mem(seed_hash, ptr);
   if (!is_empty()) {
-    ptr += copy_to_mem(&num_coupons, ptr, sizeof(num_coupons));
+    ptr += copy_to_mem(num_coupons, ptr);
     if (has_table && has_window) {
       // if there is no window it is the same as number of coupons
-      ptr += copy_to_mem(&compressed.table_num_entries, ptr, sizeof(compressed.table_num_entries));
+      ptr += copy_to_mem(compressed.table_num_entries, ptr);
       // HIP values can be in two different places in the sequence of fields
       // this is the first HIP decision point
       if (has_hip) ptr += copy_hip_to_mem(ptr);
     }
     if (has_table) {
-      ptr += copy_to_mem(&compressed.table_data_words, ptr, sizeof(compressed.table_data_words));
+      ptr += copy_to_mem(compressed.table_data_words, ptr);
     }
     if (has_window) {
-      ptr += copy_to_mem(&compressed.window_data_words, ptr, sizeof(compressed.window_data_words));
+      ptr += copy_to_mem(compressed.window_data_words, ptr);
     }
     // this is the second HIP decision point
     if (has_hip && !(has_table && has_window)) ptr += copy_hip_to_mem(ptr);
@@ -517,20 +517,13 @@
 
 template<typename A>
 cpc_sketch_alloc<A> cpc_sketch_alloc<A>::deserialize(std::istream& is, uint64_t seed, const A& allocator) {
-  uint8_t preamble_ints;
-  is.read((char*)&preamble_ints, sizeof(preamble_ints));
-  uint8_t serial_version;
-  is.read((char*)&serial_version, sizeof(serial_version));
-  uint8_t family_id;
-  is.read((char*)&family_id, sizeof(family_id));
-  uint8_t lg_k;
-  is.read((char*)&lg_k, sizeof(lg_k));
-  uint8_t first_interesting_column;
-  is.read((char*)&first_interesting_column, sizeof(first_interesting_column));
-  uint8_t flags_byte;
-  is.read((char*)&flags_byte, sizeof(flags_byte));
-  uint16_t seed_hash;
-  is.read((char*)&seed_hash, sizeof(seed_hash));
+  const auto preamble_ints = read<uint8_t>(is);
+  const auto serial_version = read<uint8_t>(is);
+  const auto family_id = read<uint8_t>(is);
+  const auto lg_k = read<uint8_t>(is);
+  const auto first_interesting_column = read<uint8_t>(is);
+  const auto flags_byte = read<uint8_t>(is);
+  const auto seed_hash = read<uint16_t>(is);
   const bool has_hip = flags_byte & (1 << flags::HAS_HIP);
   const bool has_table = flags_byte & (1 << flags::HAS_TABLE);
   const bool has_window = flags_byte & (1 << flags::HAS_WINDOW);
@@ -542,31 +535,31 @@
   double kxp = 0;
   double hip_est_accum = 0;
   if (has_table || has_window) {
-    is.read((char*)&num_coupons, sizeof(num_coupons));
+    num_coupons = read<uint32_t>(is);
     if (has_table && has_window) {
-      is.read((char*)&compressed.table_num_entries, sizeof(compressed.table_num_entries));
+      compressed.table_num_entries = read<uint32_t>(is);
       if (has_hip) {
-        is.read((char*)&kxp, sizeof(kxp));
-        is.read((char*)&hip_est_accum, sizeof(hip_est_accum));
+        kxp = read<double>(is);
+        hip_est_accum = read<double>(is);
       }
     }
     if (has_table) {
-      is.read((char*)&compressed.table_data_words, sizeof(compressed.table_data_words));
+      compressed.table_data_words = read<uint32_t>(is);
     }
     if (has_window) {
-      is.read((char*)&compressed.window_data_words, sizeof(compressed.window_data_words));
+      compressed.window_data_words = read<uint32_t>(is);
     }
     if (has_hip && !(has_table && has_window)) {
-      is.read((char*)&kxp, sizeof(kxp));
-      is.read((char*)&hip_est_accum, sizeof(hip_est_accum));
+      kxp = read<double>(is);
+      hip_est_accum = read<double>(is);
     }
     if (has_window) {
       compressed.window_data.resize(compressed.window_data_words);
-      is.read((char*)compressed.window_data.data(), compressed.window_data_words * sizeof(uint32_t));
+      is.read(reinterpret_cast<char*>(compressed.window_data.data()), compressed.window_data_words * sizeof(uint32_t));
     }
     if (has_table) {
       compressed.table_data.resize(compressed.table_data_words);
-      is.read((char*)compressed.table_data.data(), compressed.table_data_words * sizeof(uint32_t));
+      is.read(reinterpret_cast<char*>(compressed.table_data.data()), compressed.table_data_words * sizeof(uint32_t));
     }
     if (!has_window) compressed.table_num_entries = num_coupons;
   }
@@ -602,19 +595,19 @@
   const char* ptr = static_cast<const char*>(bytes);
   const char* base = static_cast<const char*>(bytes);
   uint8_t preamble_ints;
-  ptr += copy_from_mem(ptr, &preamble_ints, sizeof(preamble_ints));
+  ptr += copy_from_mem(ptr, preamble_ints);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(serial_version));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t family_id;
-  ptr += copy_from_mem(ptr, &family_id, sizeof(family_id));
+  ptr += copy_from_mem(ptr, family_id);
   uint8_t lg_k;
-  ptr += copy_from_mem(ptr, &lg_k, sizeof(lg_k));
+  ptr += copy_from_mem(ptr, lg_k);
   uint8_t first_interesting_column;
-  ptr += copy_from_mem(ptr, &first_interesting_column, sizeof(first_interesting_column));
+  ptr += copy_from_mem(ptr, first_interesting_column);
   uint8_t flags_byte;
-  ptr += copy_from_mem(ptr, &flags_byte, sizeof(flags_byte));
+  ptr += copy_from_mem(ptr, flags_byte);
   uint16_t seed_hash;
-  ptr += copy_from_mem(ptr, &seed_hash, sizeof(seed_hash));
+  ptr += copy_from_mem(ptr, seed_hash);
   const bool has_hip = flags_byte & (1 << flags::HAS_HIP);
   const bool has_table = flags_byte & (1 << flags::HAS_TABLE);
   const bool has_window = flags_byte & (1 << flags::HAS_WINDOW);
@@ -628,28 +621,28 @@
   double hip_est_accum = 0;
   if (has_table || has_window) {
     check_memory_size(ptr - base + sizeof(num_coupons), size);
-    ptr += copy_from_mem(ptr, &num_coupons, sizeof(num_coupons));
+    ptr += copy_from_mem(ptr, num_coupons);
     if (has_table && has_window) {
       check_memory_size(ptr - base + sizeof(compressed.table_num_entries), size);
-      ptr += copy_from_mem(ptr, &compressed.table_num_entries, sizeof(compressed.table_num_entries));
+      ptr += copy_from_mem(ptr, compressed.table_num_entries);
       if (has_hip) {
         check_memory_size(ptr - base + sizeof(kxp) + sizeof(hip_est_accum), size);
-        ptr += copy_from_mem(ptr, &kxp, sizeof(kxp));
-        ptr += copy_from_mem(ptr, &hip_est_accum, sizeof(hip_est_accum));
+        ptr += copy_from_mem(ptr, kxp);
+        ptr += copy_from_mem(ptr, hip_est_accum);
       }
     }
     if (has_table) {
       check_memory_size(ptr - base + sizeof(compressed.table_data_words), size);
-      ptr += copy_from_mem(ptr, &compressed.table_data_words, sizeof(compressed.table_data_words));
+      ptr += copy_from_mem(ptr, compressed.table_data_words);
     }
     if (has_window) {
       check_memory_size(ptr - base + sizeof(compressed.window_data_words), size);
-      ptr += copy_from_mem(ptr, &compressed.window_data_words, sizeof(compressed.window_data_words));
+      ptr += copy_from_mem(ptr, compressed.window_data_words);
     }
     if (has_hip && !(has_table && has_window)) {
       check_memory_size(ptr - base + sizeof(kxp) + sizeof(hip_est_accum), size);
-      ptr += copy_from_mem(ptr, &kxp, sizeof(kxp));
-      ptr += copy_from_mem(ptr, &hip_est_accum, sizeof(hip_est_accum));
+      ptr += copy_from_mem(ptr, kxp);
+      ptr += copy_from_mem(ptr, hip_est_accum);
     }
     if (has_window) {
       compressed.window_data.resize(compressed.window_data_words);
@@ -799,8 +792,8 @@
 
 template<typename A>
 void cpc_sketch_alloc<A>::write_hip(std::ostream& os) const {
-  os.write(reinterpret_cast<const char*>(&kxp), sizeof(kxp));
-  os.write(reinterpret_cast<const char*>(&hip_est_accum), sizeof(hip_est_accum));
+  write(os, kxp);
+  write(os, hip_est_accum);
 }
 
 template<typename A>
diff --git a/fi/include/frequent_items_sketch_impl.hpp b/fi/include/frequent_items_sketch_impl.hpp
index df4352e..a945c94 100644
--- a/fi/include/frequent_items_sketch_impl.hpp
+++ b/fi/include/frequent_items_sketch_impl.hpp
@@ -162,28 +162,28 @@
 template<typename T, typename W, typename H, typename E, typename S, typename A>
 void frequent_items_sketch<T, W, H, E, S, A>::serialize(std::ostream& os) const {
   const uint8_t preamble_longs = is_empty() ? PREAMBLE_LONGS_EMPTY : PREAMBLE_LONGS_NONEMPTY;
-  os.write((char*)&preamble_longs, sizeof(preamble_longs));
+  write(os, preamble_longs);
   const uint8_t serial_version = SERIAL_VERSION;
-  os.write((char*)&serial_version, sizeof(serial_version));
+  write(os, serial_version);
   const uint8_t family = FAMILY_ID;
-  os.write((char*)&family, sizeof(family));
+  write(os, family);
   const uint8_t lg_max_size = map.get_lg_max_size();
-  os.write((char*)&lg_max_size, sizeof(lg_max_size));
+  write(os, lg_max_size);
   const uint8_t lg_cur_size = map.get_lg_cur_size();
-  os.write((char*)&lg_cur_size, sizeof(lg_cur_size));
+  write(os, lg_cur_size);
   const uint8_t flags_byte(
     (is_empty() ? 1 << flags::IS_EMPTY : 0)
   );
-  os.write((char*)&flags_byte, sizeof(flags_byte));
+  write(os, flags_byte);
   const uint16_t unused16 = 0;
-  os.write((char*)&unused16, sizeof(unused16));
+  write(os, unused16);
   if (!is_empty()) {
     const uint32_t num_items = map.get_num_active();
-    os.write((char*)&num_items, sizeof(num_items));
+    write(os, num_items);
     const uint32_t unused32 = 0;
-    os.write((char*)&unused32, sizeof(unused32));
-    os.write((char*)&total_weight, sizeof(total_weight));
-    os.write((char*)&offset, sizeof(offset));
+    write(os, unused32);
+    write(os, total_weight);
+    write(os, offset);
 
     // copy active items and their weights to use batch serialization
     using AllocW = typename std::allocator_traits<A>::template rebind_alloc<W>;
@@ -196,7 +196,7 @@
       new (&items[i]) T(it.first);
       weights[i++] = it.second;
     }
-    os.write((char*)weights, sizeof(W) * num_items);
+    os.write(reinterpret_cast<const char*>(weights), sizeof(W) * num_items);
     aw.deallocate(weights, num_items);
     S().serialize(os, items, num_items);
     for (unsigned i = 0; i < num_items; i++) items[i].~T();
@@ -220,28 +220,28 @@
   uint8_t* end_ptr = ptr + size;
 
   const uint8_t preamble_longs = is_empty() ? PREAMBLE_LONGS_EMPTY : PREAMBLE_LONGS_NONEMPTY;
-  ptr += copy_to_mem(&preamble_longs, ptr, sizeof(uint8_t));
+  ptr += copy_to_mem(preamble_longs, ptr);
   const uint8_t serial_version = SERIAL_VERSION;
-  ptr += copy_to_mem(&serial_version, ptr, sizeof(uint8_t));
+  ptr += copy_to_mem(serial_version, ptr);
   const uint8_t family = FAMILY_ID;
-  ptr += copy_to_mem(&family, ptr, sizeof(uint8_t));
+  ptr += copy_to_mem(family, ptr);
   const uint8_t lg_max_size = map.get_lg_max_size();
-  ptr += copy_to_mem(&lg_max_size, ptr, sizeof(uint8_t));
+  ptr += copy_to_mem(lg_max_size, ptr);
   const uint8_t lg_cur_size = map.get_lg_cur_size();
-  ptr += copy_to_mem(&lg_cur_size, ptr, sizeof(uint8_t));
+  ptr += copy_to_mem(lg_cur_size, ptr);
   const uint8_t flags_byte(
     (is_empty() ? 1 << flags::IS_EMPTY : 0)
   );
-  ptr += copy_to_mem(&flags_byte, ptr, sizeof(uint8_t));
+  ptr += copy_to_mem(flags_byte, ptr);
   const uint16_t unused16 = 0;
-  ptr += copy_to_mem(&unused16, ptr, sizeof(uint16_t));
+  ptr += copy_to_mem(unused16, ptr);
   if (!is_empty()) {
     const uint32_t num_items = map.get_num_active();
-    ptr += copy_to_mem(&num_items, ptr, sizeof(uint32_t));
+    ptr += copy_to_mem(num_items, ptr);
     const uint32_t unused32 = 0;
-    ptr += copy_to_mem(&unused32, ptr, sizeof(uint32_t));
-    ptr += copy_to_mem(&total_weight, ptr, sizeof(total_weight));
-    ptr += copy_to_mem(&offset, ptr, sizeof(offset));
+    ptr += copy_to_mem(unused32, ptr);
+    ptr += copy_to_mem(total_weight, ptr);
+    ptr += copy_to_mem(offset, ptr);
 
     // copy active items and their weights to use batch serialization
     using AllocW = typename std::allocator_traits<A>::template rebind_alloc<W>;
@@ -286,20 +286,13 @@
 
 template<typename T, typename W, typename H, typename E, typename S, typename A>
 frequent_items_sketch<T, W, H, E, S, A> frequent_items_sketch<T, W, H, E, S, A>::deserialize(std::istream& is, const A& allocator) {
-  uint8_t preamble_longs;
-  is.read((char*)&preamble_longs, sizeof(preamble_longs));
-  uint8_t serial_version;
-  is.read((char*)&serial_version, sizeof(serial_version));
-  uint8_t family_id;
-  is.read((char*)&family_id, sizeof(family_id));
-  uint8_t lg_max_size;
-  is.read((char*)&lg_max_size, sizeof(lg_max_size));
-  uint8_t lg_cur_size;
-  is.read((char*)&lg_cur_size, sizeof(lg_cur_size));
-  uint8_t flags_byte;
-  is.read((char*)&flags_byte, sizeof(flags_byte));
-  uint16_t unused16;
-  is.read((char*)&unused16, sizeof(unused16));
+  const auto preamble_longs = read<uint8_t>(is);
+  const auto serial_version = read<uint8_t>(is);
+  const auto family_id = read<uint8_t>(is);
+  const auto lg_max_size = read<uint8_t>(is);
+  const auto lg_cur_size = read<uint8_t>(is);
+  const auto flags_byte = read<uint8_t>(is);
+  read<uint16_t>(is); // unused
 
   const bool is_empty = flags_byte & (1 << flags::IS_EMPTY);
 
@@ -310,19 +303,15 @@
 
   frequent_items_sketch<T, W, H, E, S, A> sketch(lg_max_size, lg_cur_size, allocator);
   if (!is_empty) {
-    uint32_t num_items;
-    is.read((char*)&num_items, sizeof(num_items));
-    uint32_t unused32;
-    is.read((char*)&unused32, sizeof(unused32));
-    W total_weight;
-    is.read((char*)&total_weight, sizeof(total_weight));
-    W offset;
-    is.read((char*)&offset, sizeof(offset));
+    const auto num_items = read<uint32_t>(is);
+    read<uint32_t>(is); // unused
+    const auto total_weight = read<W>(is);
+    const auto offset = read<W>(is);
 
     // batch deserialization with intermediate array of items and weights
     using AllocW = typename std::allocator_traits<A>::template rebind_alloc<W>;
     std::vector<W, AllocW> weights(num_items, 0, allocator);
-    is.read((char*)weights.data(), sizeof(W) * num_items);
+    is.read(reinterpret_cast<char*>(weights.data()), sizeof(W) * num_items);
     A alloc(allocator);
     std::unique_ptr<T, items_deleter> items(alloc.allocate(num_items), items_deleter(num_items, false, alloc));
     S().deserialize(is, items.get(), num_items);
@@ -344,19 +333,18 @@
   const char* ptr = static_cast<const char*>(bytes);
   const char* base = static_cast<const char*>(bytes);
   uint8_t preamble_longs;
-  ptr += copy_from_mem(ptr, &preamble_longs, sizeof(uint8_t));
+  ptr += copy_from_mem(ptr, preamble_longs);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(uint8_t));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t family_id;
-  ptr += copy_from_mem(ptr, &family_id, sizeof(uint8_t));
+  ptr += copy_from_mem(ptr, family_id);
   uint8_t lg_max_size;
-  ptr += copy_from_mem(ptr, &lg_max_size, sizeof(uint8_t));
+  ptr += copy_from_mem(ptr, lg_max_size);
   uint8_t lg_cur_size;
-  ptr += copy_from_mem(ptr, &lg_cur_size, sizeof(uint8_t));
+  ptr += copy_from_mem(ptr, lg_cur_size);
   uint8_t flags_byte;
-  ptr += copy_from_mem(ptr, &flags_byte, sizeof(uint8_t));
-  uint16_t unused16;
-  ptr += copy_from_mem(ptr, &unused16, sizeof(uint16_t));
+  ptr += copy_from_mem(ptr, flags_byte);
+  ptr += sizeof(uint16_t); // unused
 
   const bool is_empty = flags_byte & (1 << flags::IS_EMPTY);
 
@@ -369,13 +357,12 @@
   frequent_items_sketch<T, W, H, E, S, A> sketch(lg_max_size, lg_cur_size, allocator);
   if (!is_empty) {
     uint32_t num_items;
-    ptr += copy_from_mem(ptr, &num_items, sizeof(uint32_t));
-    uint32_t unused32;
-    ptr += copy_from_mem(ptr, &unused32, sizeof(uint32_t));
+    ptr += copy_from_mem(ptr, num_items);
+    ptr += sizeof(uint32_t); // unused
     W total_weight;
-    ptr += copy_from_mem(ptr, &total_weight, sizeof(total_weight));
+    ptr += copy_from_mem(ptr, total_weight);
     W offset;
-    ptr += copy_from_mem(ptr, &offset, sizeof(offset));
+    ptr += copy_from_mem(ptr, offset);
 
     ensure_minimum_memory(size, ptr - base + (sizeof(W) * num_items));
     // batch deserialization with intermediate array of items and weights
diff --git a/kll/include/kll_sketch_impl.hpp b/kll/include/kll_sketch_impl.hpp
index 0e0ef87..59102e5 100644
--- a/kll/include/kll_sketch_impl.hpp
+++ b/kll/include/kll_sketch_impl.hpp
@@ -388,28 +388,28 @@
 void kll_sketch<T, C, S, A>::serialize(std::ostream& os) const {
   const bool is_single_item = n_ == 1;
   const uint8_t preamble_ints(is_empty() || is_single_item ? PREAMBLE_INTS_SHORT : PREAMBLE_INTS_FULL);
-  os.write(reinterpret_cast<const char*>(&preamble_ints), sizeof(preamble_ints));
+  write(os, preamble_ints);
   const uint8_t serial_version(is_single_item ? SERIAL_VERSION_2 : SERIAL_VERSION_1);
-  os.write(reinterpret_cast<const char*>(&serial_version), sizeof(serial_version));
+  write(os, serial_version);
   const uint8_t family(FAMILY);
-  os.write(reinterpret_cast<const char*>(&family), sizeof(family));
+  write(os, family);
   const uint8_t flags_byte(
       (is_empty() ? 1 << flags::IS_EMPTY : 0)
     | (is_level_zero_sorted_ ? 1 << flags::IS_LEVEL_ZERO_SORTED : 0)
     | (is_single_item ? 1 << flags::IS_SINGLE_ITEM : 0)
   );
-  os.write(reinterpret_cast<const char*>(&flags_byte), sizeof(flags_byte));
-  os.write((char*)&k_, sizeof(k_));
-  os.write((char*)&m_, sizeof(m_));
+  write(os, flags_byte);
+  write(os, k_);
+  write(os, m_);
   const uint8_t unused = 0;
-  os.write(reinterpret_cast<const char*>(&unused), sizeof(unused));
+  write(os, unused);
   if (is_empty()) return;
   if (!is_single_item) {
-    os.write((char*)&n_, sizeof(n_));
-    os.write((char*)&min_k_, sizeof(min_k_));
-    os.write((char*)&num_levels_, sizeof(num_levels_));
-    os.write((char*)&unused, sizeof(unused));
-    os.write((char*)levels_.data(), sizeof(levels_[0]) * num_levels_);
+    write(os, n_);
+    write(os, min_k_);
+    write(os, num_levels_);
+    write(os, unused);
+    os.write(reinterpret_cast<const char*>(levels_.data()), sizeof(levels_[0]) * num_levels_);
     S().serialize(os, min_value_, 1);
     S().serialize(os, max_value_, 1);
   }
@@ -424,27 +424,27 @@
   uint8_t* ptr = bytes.data() + header_size_bytes;
   const uint8_t* end_ptr = ptr + size;
   const uint8_t preamble_ints(is_empty() || is_single_item ? PREAMBLE_INTS_SHORT : PREAMBLE_INTS_FULL);
-  ptr += copy_to_mem(&preamble_ints, ptr, sizeof(preamble_ints));
+  ptr += copy_to_mem(preamble_ints, ptr);
   const uint8_t serial_version(is_single_item ? SERIAL_VERSION_2 : SERIAL_VERSION_1);
-  ptr += copy_to_mem(&serial_version, ptr, sizeof(serial_version));
+  ptr += copy_to_mem(serial_version, ptr);
   const uint8_t family(FAMILY);
-  ptr += copy_to_mem(&family, ptr, sizeof(family));
+  ptr += copy_to_mem(family, ptr);
   const uint8_t flags_byte(
       (is_empty() ? 1 << flags::IS_EMPTY : 0)
     | (is_level_zero_sorted_ ? 1 << flags::IS_LEVEL_ZERO_SORTED : 0)
     | (is_single_item ? 1 << flags::IS_SINGLE_ITEM : 0)
   );
-  ptr += copy_to_mem(&flags_byte, ptr, sizeof(flags_byte));
-  ptr += copy_to_mem(&k_, ptr, sizeof(k_));
-  ptr += copy_to_mem(&m_, ptr, sizeof(m_));
+  ptr += copy_to_mem(flags_byte, ptr);
+  ptr += copy_to_mem(k_, ptr);
+  ptr += copy_to_mem(m_, ptr);
   const uint8_t unused = 0;
-  ptr += copy_to_mem(&unused, ptr, sizeof(unused));
+  ptr += copy_to_mem(unused, ptr);
   if (!is_empty()) {
     if (!is_single_item) {
-      ptr += copy_to_mem(&n_, ptr, sizeof(n_));
-      ptr += copy_to_mem(&min_k_, ptr, sizeof(min_k_));
-      ptr += copy_to_mem(&num_levels_, ptr, sizeof(num_levels_));
-      ptr += copy_to_mem(&unused, ptr, sizeof(unused));
+      ptr += copy_to_mem(n_, ptr);
+      ptr += copy_to_mem(min_k_, ptr);
+      ptr += copy_to_mem(num_levels_, ptr);
+      ptr += copy_to_mem(unused, ptr);
       ptr += copy_to_mem(levels_.data(), ptr, sizeof(levels_[0]) * num_levels_);
       ptr += S().serialize(ptr, end_ptr - ptr, min_value_, 1);
       ptr += S().serialize(ptr, end_ptr - ptr, max_value_, 1);
@@ -459,20 +459,13 @@
 
 template<typename T, typename C, typename S, typename A>
 kll_sketch<T, C, S, A> kll_sketch<T, C, S, A>::deserialize(std::istream& is, const A& allocator) {
-  uint8_t preamble_ints;
-  is.read((char*)&preamble_ints, sizeof(preamble_ints));
-  uint8_t serial_version;
-  is.read((char*)&serial_version, sizeof(serial_version));
-  uint8_t family_id;
-  is.read((char*)&family_id, sizeof(family_id));
-  uint8_t flags_byte;
-  is.read((char*)&flags_byte, sizeof(flags_byte));
-  uint16_t k;
-  is.read((char*)&k, sizeof(k));
-  uint8_t m;
-  is.read((char*)&m, sizeof(m));
-  uint8_t unused;
-  is.read((char*)&unused, sizeof(unused));
+  const auto preamble_ints = read<uint8_t>(is);
+  const auto serial_version = read<uint8_t>(is);
+  const auto family_id = read<uint8_t>(is);
+  const auto flags_byte = read<uint8_t>(is);
+  const auto k = read<uint16_t>(is);
+  const auto m = read<uint8_t>(is);
+  read<uint8_t>(is); // skip unused byte
 
   check_m(m);
   check_preamble_ints(preamble_ints, flags_byte);
@@ -492,10 +485,10 @@
     min_k = k;
     num_levels = 1;
   } else {
-    is.read((char*)&n, sizeof(n_));
-    is.read((char*)&min_k, sizeof(min_k_));
-    is.read((char*)&num_levels, sizeof(num_levels));
-    is.read((char*)&unused, sizeof(unused));
+    n = read<uint64_t>(is);
+    min_k = read<uint16_t>(is);
+    num_levels = read<uint8_t>(is);
+    read<uint8_t>(is); // skip unused byte
   }
   vector_u32<A> levels(num_levels + 1, 0, allocator);
   const uint32_t capacity(kll_helper::compute_total_capacity(k, m, num_levels));
@@ -503,7 +496,7 @@
     levels[0] = capacity - 1;
   } else {
     // the last integer in levels_ is not serialized because it can be derived
-    is.read((char*)levels.data(), sizeof(levels[0]) * num_levels);
+    is.read(reinterpret_cast<char*>(levels.data()), sizeof(levels[0]) * num_levels);
   }
   levels[num_levels] = capacity;
   A alloc(allocator);
@@ -546,18 +539,18 @@
   ensure_minimum_memory(size, 8);
   const char* ptr = static_cast<const char*>(bytes);
   uint8_t preamble_ints;
-  ptr += copy_from_mem(ptr, &preamble_ints, sizeof(preamble_ints));
+  ptr += copy_from_mem(ptr, preamble_ints);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(serial_version));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t family_id;
-  ptr += copy_from_mem(ptr, &family_id, sizeof(family_id));
+  ptr += copy_from_mem(ptr, family_id);
   uint8_t flags_byte;
-  ptr += copy_from_mem(ptr, &flags_byte, sizeof(flags_byte));
+  ptr += copy_from_mem(ptr, flags_byte);
   uint16_t k;
-  ptr += copy_from_mem(ptr, &k, sizeof(k));
+  ptr += copy_from_mem(ptr, k);
   uint8_t m;
-  ptr += copy_from_mem(ptr, &m, sizeof(m));
-  ptr++; // skip unused byte
+  ptr += copy_from_mem(ptr, m);
+  ptr += sizeof(uint8_t); // skip unused byte
 
   check_m(m);
   check_preamble_ints(preamble_ints, flags_byte);
@@ -578,10 +571,10 @@
     min_k = k;
     num_levels = 1;
   } else {
-    ptr += copy_from_mem(ptr, &n, sizeof(n));
-    ptr += copy_from_mem(ptr, &min_k, sizeof(min_k));
-    ptr += copy_from_mem(ptr, &num_levels, sizeof(num_levels));
-    ptr++; // skip unused byte
+    ptr += copy_from_mem(ptr, n);
+    ptr += copy_from_mem(ptr, min_k);
+    ptr += copy_from_mem(ptr, num_levels);
+    ptr += sizeof(uint8_t); // skip unused byte
   }
   vector_u32<A> levels(num_levels + 1, 0, allocator);
   const uint32_t capacity(kll_helper::compute_total_capacity(k, m, num_levels));
diff --git a/sampling/include/var_opt_sketch_impl.hpp b/sampling/include/var_opt_sketch_impl.hpp
index edc695c..6b31a16 100644
--- a/sampling/include/var_opt_sketch_impl.hpp
+++ b/sampling/include/var_opt_sketch_impl.hpp
@@ -359,21 +359,21 @@
   // first prelong
   uint8_t ser_ver(SER_VER);
   uint8_t family(FAMILY_ID);
-  ptr += copy_to_mem(&first_byte, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&ser_ver, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&family, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&flags, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&k_, ptr, sizeof(uint32_t));
+  ptr += copy_to_mem(first_byte, ptr);
+  ptr += copy_to_mem(ser_ver, ptr);
+  ptr += copy_to_mem(family, ptr);
+  ptr += copy_to_mem(flags, ptr);
+  ptr += copy_to_mem(k_, ptr);
 
   if (!empty) {
     // second and third prelongs
-    ptr += copy_to_mem(&n_, ptr, sizeof(uint64_t));
-    ptr += copy_to_mem(&h_, ptr, sizeof(uint32_t));
-    ptr += copy_to_mem(&r_, ptr, sizeof(uint32_t));
+    ptr += copy_to_mem(n_, ptr);
+    ptr += copy_to_mem(h_, ptr);
+    ptr += copy_to_mem(r_, ptr);
 
     // fourth prelong, if needed
     if (r_ > 0) {
-      ptr += copy_to_mem(&total_wt_r_, ptr, sizeof(double));
+      ptr += copy_to_mem(total_wt_r_, ptr);
     }
 
     // first h_ weights
@@ -388,14 +388,14 @@
         }
 
         if ((i & 0x7) == 0x7) {
-          ptr += copy_to_mem(&val, ptr, sizeof(uint8_t));
+          ptr += copy_to_mem(val, ptr);
           val = 0;
         }
       }
 
       // write out any remaining values
       if ((h_ & 0x7) > 0) {
-        ptr += copy_to_mem(&val, ptr, sizeof(uint8_t));
+        ptr += copy_to_mem(val, ptr);
       }
     }
 
@@ -428,25 +428,25 @@
   // first prelong
   const uint8_t ser_ver(SER_VER);
   const uint8_t family(FAMILY_ID);
-  os.write((char*)&first_byte, sizeof(uint8_t));
-  os.write((char*)&ser_ver, sizeof(uint8_t));
-  os.write((char*)&family, sizeof(uint8_t));
-  os.write((char*)&flags, sizeof(uint8_t));
-  os.write((char*)&k_, sizeof(uint32_t));
+  write(os, first_byte);
+  write(os, ser_ver);
+  write(os, family);
+  write(os, flags);
+  write(os, k_);
 
   if (!empty) {
     // second and third prelongs
-    os.write((char*)&n_, sizeof(uint64_t));
-    os.write((char*)&h_, sizeof(uint32_t));
-    os.write((char*)&r_, sizeof(uint32_t));
+    write(os, n_);
+    write(os, h_);
+    write(os, r_);
     
     // fourth prelong, if needed
     if (r_ > 0) {
-      os.write((char*)&total_wt_r_, sizeof(double));
+      write(os, total_wt_r_);
     }
 
     // write the first h_ weights
-    os.write((char*)weights_, h_ * sizeof(double));
+    os.write(reinterpret_cast<char*>(weights_), h_ * sizeof(double));
 
     // write the first h_ marks as packed bytes iff we have a gadget
     if (marks_ != nullptr) {
@@ -457,14 +457,14 @@
         }
 
         if ((i & 0x7) == 0x7) {
-          os.write((char*)&val, sizeof(uint8_t));
+          write(os, val);
           val = 0;
         }
       }
 
       // write out any remaining values
       if ((h_ & 0x7) > 0) {
-        os.write((char*)&val, sizeof(uint8_t));
+        write(os, val);
       }
     }
 
@@ -481,17 +481,17 @@
   const char* base = ptr;
   const char* end_ptr = ptr + size;
   uint8_t first_byte;
-  ptr += copy_from_mem(ptr, &first_byte, sizeof(first_byte));
+  ptr += copy_from_mem(ptr, first_byte);
   uint8_t preamble_longs = first_byte & 0x3f;
   resize_factor rf = static_cast<resize_factor>((first_byte >> 6) & 0x03);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(serial_version));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t family_id;
-  ptr += copy_from_mem(ptr, &family_id, sizeof(family_id));
+  ptr += copy_from_mem(ptr, family_id);
   uint8_t flags;
-  ptr += copy_from_mem(ptr, &flags, sizeof(flags));
+  ptr += copy_from_mem(ptr, flags);
   uint32_t k;
-  ptr += copy_from_mem(ptr, &k, sizeof(k));
+  ptr += copy_from_mem(ptr, k);
 
   check_preamble_longs(preamble_longs, flags);
   check_family_and_serialization_version(family_id, serial_version);
@@ -507,16 +507,16 @@
   // second and third prelongs
   uint64_t n;
   uint32_t h, r;
-  ptr += copy_from_mem(ptr, &n, sizeof(n));
-  ptr += copy_from_mem(ptr, &h, sizeof(h));
-  ptr += copy_from_mem(ptr, &r, sizeof(r));
+  ptr += copy_from_mem(ptr, n);
+  ptr += copy_from_mem(ptr, h);
+  ptr += copy_from_mem(ptr, r);
 
   const uint32_t array_size = validate_and_get_target_size(preamble_longs, k, n, h, r, rf);
   
   // current_items_alloc_ is set but validate R region weight (4th prelong), if needed, before allocating
   double total_wt_r = 0.0;
   if (preamble_longs == PREAMBLE_LONGS_FULL) {
-    ptr += copy_from_mem(ptr, &total_wt_r, sizeof(total_wt_r));
+    ptr += copy_from_mem(ptr, total_wt_r);
     if (std::isnan(total_wt_r) || r == 0 || total_wt_r <= 0.0) {
       throw std::invalid_argument("Possible corruption: deserializing in full mode but r = 0 or invalid R weight. "
        "Found r = " + std::to_string(r) + ", R region weight = " + std::to_string(total_wt_r));
@@ -548,7 +548,7 @@
     check_memory_size(ptr - base + size_marks, size);
     for (uint32_t i = 0; i < h; ++i) {
      if ((i & 0x7) == 0x0) { // should trigger on first iteration
-        ptr += copy_from_mem(ptr, &val, sizeof(val));
+        ptr += copy_from_mem(ptr, val);
       }
       marks.get()[i] = ((val >> (i & 0x7)) & 0x1) == 1;
       num_marks_in_h += (marks.get()[i] ? 1 : 0);
@@ -571,18 +571,13 @@
 
 template<typename T, typename S, typename A>
 var_opt_sketch<T,S,A> var_opt_sketch<T,S,A>::deserialize(std::istream& is, const A& allocator) {
-  uint8_t first_byte;
-  is.read((char*)&first_byte, sizeof(first_byte));
+  const auto first_byte = read<uint8_t>(is);
   uint8_t preamble_longs = first_byte & 0x3f;
-  resize_factor rf = static_cast<resize_factor>((first_byte >> 6) & 0x03);
-  uint8_t serial_version;
-  is.read((char*)&serial_version, sizeof(serial_version));
-  uint8_t family_id;
-  is.read((char*)&family_id, sizeof(family_id));
-  uint8_t flags;
-  is.read((char*)&flags, sizeof(flags));
-  uint32_t k;
-  is.read((char*)&k, sizeof(k));
+  const resize_factor rf = static_cast<resize_factor>((first_byte >> 6) & 0x03);
+  const auto serial_version = read<uint8_t>(is);
+  const auto family_id = read<uint8_t>(is);
+  const auto flags = read<uint8_t>(is);
+  const auto k = read<uint32_t>(is);
 
   check_preamble_longs(preamble_longs, flags);
   check_family_and_serialization_version(family_id, serial_version);
@@ -598,31 +593,27 @@
   }
 
   // second and third prelongs
-  uint64_t n;
-  uint32_t h, r;
-  is.read((char*)&n, sizeof(n));
-  is.read((char*)&h, sizeof(h));
-  is.read((char*)&r, sizeof(r));
+  const auto n = read<uint64_t>(is);
+  const auto h = read<uint32_t>(is);
+  const auto r = read<uint32_t>(is);
 
   const uint32_t array_size = validate_and_get_target_size(preamble_longs, k, n, h, r, rf);
 
   // current_items_alloc_ is set but validate R region weight (4th prelong), if needed, before allocating
   double total_wt_r = 0.0;
   if (preamble_longs == PREAMBLE_LONGS_FULL) { 
-    is.read((char*)&total_wt_r, sizeof(total_wt_r));
+    total_wt_r = read<double>(is);
     if (std::isnan(total_wt_r) || r == 0 || total_wt_r <= 0.0) {
       throw std::invalid_argument("Possible corruption: deserializing in full mode but r = 0 or invalid R weight. "
        "Found r = " + std::to_string(r) + ", R region weight = " + std::to_string(total_wt_r));
     }
-  } else {
-    total_wt_r = 0.0;
   }
 
   // read the first h weights, fill remainder with -1.0
   std::unique_ptr<double, weights_deleter> weights(AllocDouble(allocator).allocate(array_size),
       weights_deleter(array_size, allocator));
   double* wts = weights.get(); // to avoid lots of .get() calls -- do not delete
-  is.read((char*)wts, h * sizeof(double));
+  is.read(reinterpret_cast<char*>(wts), h * sizeof(double));
   for (size_t i = 0; i < h; ++i) {
     if (!(wts[i] > 0.0)) {
       throw std::invalid_argument("Possible corruption: Non-positive weight when deserializing: " + std::to_string(wts[i]));
@@ -638,7 +629,7 @@
     uint8_t val = 0;
     for (uint32_t i = 0; i < h; ++i) {
       if ((i & 0x7) == 0x0) { // should trigger on first iteration
-        is.read((char*)&val, sizeof(val));
+        val = read<uint8_t>(is);
       }
       marks.get()[i] = ((val >> (i & 0x7)) & 0x1) == 1;
       num_marks_in_h += (marks.get()[i] ? 1 : 0);
diff --git a/sampling/include/var_opt_union_impl.hpp b/sampling/include/var_opt_union_impl.hpp
index db26bc0..3752016 100644
--- a/sampling/include/var_opt_union_impl.hpp
+++ b/sampling/include/var_opt_union_impl.hpp
@@ -129,16 +129,11 @@
 
 template<typename T, typename S, typename A>
 var_opt_union<T,S,A> var_opt_union<T,S,A>::deserialize(std::istream& is, const A& allocator) {
-  uint8_t preamble_longs;
-  is.read((char*)&preamble_longs, sizeof(preamble_longs));
-  uint8_t serial_version;
-  is.read((char*)&serial_version, sizeof(serial_version));
-  uint8_t family_id;
-  is.read((char*)&family_id, sizeof(family_id));
-  uint8_t flags;
-  is.read((char*)&flags, sizeof(flags));
-  uint32_t max_k;
-  is.read((char*)&max_k, sizeof(max_k));
+  const auto preamble_longs = read<uint8_t>(is);
+  const auto serial_version = read<uint8_t>(is);
+  const auto family_id = read<uint8_t>(is);
+  const auto flags = read<uint8_t>(is);
+  const auto max_k = read<uint32_t>(is);
 
   check_preamble_longs(preamble_longs, flags);
   check_family_and_serialization_version(family_id, serial_version);
@@ -156,12 +151,9 @@
       return var_opt_union<T,S,A>(max_k);
   }
 
-  uint64_t items_seen;
-  is.read((char*)&items_seen, sizeof(items_seen));
-  double outer_tau_numer;
-  is.read((char*)&outer_tau_numer, sizeof(outer_tau_numer));
-  uint64_t outer_tau_denom;
-  is.read((char*)&outer_tau_denom, sizeof(outer_tau_denom));
+  const auto items_seen = read<uint64_t>(is);
+  const auto outer_tau_numer = read<double>(is);
+  const auto outer_tau_denom = read<uint64_t>(is);
 
   var_opt_sketch<T,S,A> gadget = var_opt_sketch<T,S,A>::deserialize(is, allocator);
 
@@ -176,15 +168,15 @@
   ensure_minimum_memory(size, 8);
   const char* ptr = static_cast<const char*>(bytes);
   uint8_t preamble_longs;
-  ptr += copy_from_mem(ptr, &preamble_longs, sizeof(preamble_longs));
+  ptr += copy_from_mem(ptr, preamble_longs);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(serial_version));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t family_id;
-  ptr += copy_from_mem(ptr, &family_id, sizeof(family_id));
+  ptr += copy_from_mem(ptr, family_id);
   uint8_t flags;
-  ptr += copy_from_mem(ptr, &flags, sizeof(flags));
+  ptr += copy_from_mem(ptr, flags);
   uint32_t max_k;
-  ptr += copy_from_mem(ptr, &max_k, sizeof(max_k));
+  ptr += copy_from_mem(ptr, max_k);
 
   check_preamble_longs(preamble_longs, flags);
   check_family_and_serialization_version(family_id, serial_version);
@@ -200,11 +192,11 @@
   }
 
   uint64_t items_seen;
-  ptr += copy_from_mem(ptr, &items_seen, sizeof(items_seen));
+  ptr += copy_from_mem(ptr, items_seen);
   double outer_tau_numer;
-  ptr += copy_from_mem(ptr, &outer_tau_numer, sizeof(outer_tau_numer));
+  ptr += copy_from_mem(ptr, outer_tau_numer);
   uint64_t outer_tau_denom;
-  ptr += copy_from_mem(ptr, &outer_tau_denom, sizeof(outer_tau_denom));
+  ptr += copy_from_mem(ptr, outer_tau_denom);
 
   const size_t gadget_size = size - (PREAMBLE_LONGS_NON_EMPTY << 3);
   var_opt_sketch<T,S,A> gadget = var_opt_sketch<T,S,A>::deserialize(ptr, gadget_size, allocator);
@@ -238,16 +230,16 @@
     flags = 0;
   }
 
-  os.write((char*) &preamble_longs, sizeof(uint8_t));
-  os.write((char*) &serialization_version, sizeof(uint8_t));
-  os.write((char*) &family_id, sizeof(uint8_t));
-  os.write((char*) &flags, sizeof(uint8_t));
-  os.write((char*) &max_k_, sizeof(uint32_t));
+  write(os, preamble_longs);
+  write(os, serialization_version);
+  write(os, family_id);
+  write(os, flags);
+  write(os, max_k_);
 
   if (!empty) {
-    os.write((char*) &n_, sizeof(uint64_t));
-    os.write((char*) &outer_tau_numer_, sizeof(double));
-    os.write((char*) &outer_tau_denom_, sizeof(uint64_t));
+    write(os, n_);
+    write(os, outer_tau_numer_);
+    write(os, outer_tau_denom_);
     gadget_.serialize(os);
   }
 }
@@ -275,16 +267,16 @@
   }
 
   // first prelong
-  ptr += copy_to_mem(&preamble_longs, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&serialization_version, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&family_id, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&flags, ptr, sizeof(uint8_t));
-  ptr += copy_to_mem(&max_k_, ptr, sizeof(uint32_t));
+  ptr += copy_to_mem(preamble_longs, ptr);
+  ptr += copy_to_mem(serialization_version, ptr);
+  ptr += copy_to_mem(family_id, ptr);
+  ptr += copy_to_mem(flags, ptr);
+  ptr += copy_to_mem(max_k_, ptr);
 
   if (!empty) {
-    ptr += copy_to_mem(&n_, ptr, sizeof(uint64_t));
-    ptr += copy_to_mem(&outer_tau_numer_, ptr, sizeof(double));
-    ptr += copy_to_mem(&outer_tau_denom_, ptr, sizeof(uint64_t));
+    ptr += copy_to_mem(n_, ptr);
+    ptr += copy_to_mem(outer_tau_numer_, ptr);
+    ptr += copy_to_mem(outer_tau_denom_, ptr);
 
     auto gadget_bytes = gadget_.serialize();
     ptr += copy_to_mem(gadget_bytes.data(), ptr, gadget_bytes.size() * sizeof(uint8_t));
diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp
index 1335e59..b7d1f10 100644
--- a/theta/include/theta_sketch_impl.hpp
+++ b/theta/include/theta_sketch_impl.hpp
@@ -325,30 +325,30 @@
 void compact_theta_sketch_alloc<A>::serialize(std::ostream& os) const {
   const bool is_single_item = entries_.size() == 1 && !this->is_estimation_mode();
   const uint8_t preamble_longs = this->is_empty() || is_single_item ? 1 : this->is_estimation_mode() ? 3 : 2;
-  os.write(reinterpret_cast<const char*>(&preamble_longs), sizeof(preamble_longs));
+  write(os, preamble_longs);
   const uint8_t serial_version = SERIAL_VERSION;
-  os.write(reinterpret_cast<const char*>(&serial_version), sizeof(serial_version));
+  write(os, serial_version);
   const uint8_t type = SKETCH_TYPE;
-  os.write(reinterpret_cast<const char*>(&type), sizeof(type));
+  write(os, type);
   const uint16_t unused16 = 0;
-  os.write(reinterpret_cast<const char*>(&unused16), sizeof(unused16));
+  write(os, unused16);
   const uint8_t flags_byte(
     (1 << flags::IS_COMPACT) |
     (1 << flags::IS_READ_ONLY) |
     (this->is_empty() ? 1 << flags::IS_EMPTY : 0) |
     (this->is_ordered() ? 1 << flags::IS_ORDERED : 0)
   );
-  os.write(reinterpret_cast<const char*>(&flags_byte), sizeof(flags_byte));
+  write(os, flags_byte);
   const uint16_t seed_hash = get_seed_hash();
-  os.write(reinterpret_cast<const char*>(&seed_hash), sizeof(seed_hash));
+  write(os, seed_hash);
   if (!this->is_empty()) {
     if (!is_single_item) {
       const uint32_t num_entries = entries_.size();
-      os.write(reinterpret_cast<const char*>(&num_entries), sizeof(num_entries));
+      write(os, num_entries);
       const uint32_t unused32 = 0;
-      os.write(reinterpret_cast<const char*>(&unused32), sizeof(unused32));
+      write(os, unused32);
       if (this->is_estimation_mode()) {
-        os.write(reinterpret_cast<const char*>(&(this->theta_)), sizeof(uint64_t));
+        write(os, this->theta_);
       }
     }
     os.write(reinterpret_cast<const char*>(entries_.data()), entries_.size() * sizeof(uint64_t));
@@ -364,30 +364,30 @@
   vector_bytes bytes(size, 0, entries_.get_allocator());
   uint8_t* ptr = bytes.data() + header_size_bytes;
 
-  ptr += copy_to_mem(&preamble_longs, ptr, sizeof(preamble_longs));
+  ptr += copy_to_mem(preamble_longs, ptr);
   const uint8_t serial_version = SERIAL_VERSION;
-  ptr += copy_to_mem(&serial_version, ptr, sizeof(serial_version));
+  ptr += copy_to_mem(serial_version, ptr);
   const uint8_t type = SKETCH_TYPE;
-  ptr += copy_to_mem(&type, ptr, sizeof(type));
+  ptr += copy_to_mem(type, ptr);
   const uint16_t unused16 = 0;
-  ptr += copy_to_mem(&unused16, ptr, sizeof(unused16));
+  ptr += copy_to_mem(unused16, ptr);
   const uint8_t flags_byte(
     (1 << flags::IS_COMPACT) |
     (1 << flags::IS_READ_ONLY) |
     (this->is_empty() ? 1 << flags::IS_EMPTY : 0) |
     (this->is_ordered() ? 1 << flags::IS_ORDERED : 0)
   );
-  ptr += copy_to_mem(&flags_byte, ptr, sizeof(flags_byte));
+  ptr += copy_to_mem(flags_byte, ptr);
   const uint16_t seed_hash = get_seed_hash();
-  ptr += copy_to_mem(&seed_hash, ptr, sizeof(seed_hash));
+  ptr += copy_to_mem(seed_hash, ptr);
   if (!this->is_empty()) {
     if (!is_single_item) {
       const uint32_t num_entries = entries_.size();
-      ptr += copy_to_mem(&num_entries, ptr, sizeof(num_entries));
+      ptr += copy_to_mem(num_entries, ptr);
       const uint32_t unused32 = 0;
-      ptr += copy_to_mem(&unused32, ptr, sizeof(unused32));
+      ptr += copy_to_mem(unused32, ptr);
       if (this->is_estimation_mode()) {
-        ptr += copy_to_mem(&theta_, ptr, sizeof(uint64_t));
+        ptr += copy_to_mem(theta_, ptr);
       }
     }
     ptr += copy_to_mem(entries_.data(), ptr, entries_.size() * sizeof(uint64_t));
@@ -397,18 +397,12 @@
 
 template<typename A>
 compact_theta_sketch_alloc<A> compact_theta_sketch_alloc<A>::deserialize(std::istream& is, uint64_t seed, const A& allocator) {
-  uint8_t preamble_longs;
-  is.read(reinterpret_cast<char*>(&preamble_longs), sizeof(preamble_longs));
-  uint8_t serial_version;
-  is.read(reinterpret_cast<char*>(&serial_version), sizeof(serial_version));
-  uint8_t type;
-  is.read(reinterpret_cast<char*>(&type), sizeof(type));
-  uint16_t unused16;
-  is.read(reinterpret_cast<char*>(&unused16), sizeof(unused16));
-  uint8_t flags_byte;
-  is.read(reinterpret_cast<char*>(&flags_byte), sizeof(flags_byte));
-  uint16_t seed_hash;
-  is.read(reinterpret_cast<char*>(&seed_hash), sizeof(seed_hash));
+  const auto preamble_longs = read<uint8_t>(is);
+  const auto serial_version = read<uint8_t>(is);
+  const auto type = read<uint8_t>(is);
+  read<uint16_t>(is); // unused
+  const auto flags_byte = read<uint8_t>(is);
+  const auto seed_hash = read<uint16_t>(is);
   checker<true>::check_sketch_type(type, SKETCH_TYPE);
   checker<true>::check_serial_version(serial_version, SERIAL_VERSION);
   const bool is_empty = flags_byte & (1 << flags::IS_EMPTY);
@@ -420,11 +414,10 @@
     if (preamble_longs == 1) {
       num_entries = 1;
     } else {
-      is.read(reinterpret_cast<char*>(&num_entries), sizeof(num_entries));
-      uint32_t unused32;
-      is.read(reinterpret_cast<char*>(&unused32), sizeof(unused32));
+      num_entries = read<uint32_t>(is);
+      read<uint32_t>(is); // unused
       if (preamble_longs > 2) {
-        is.read(reinterpret_cast<char*>(&theta), sizeof(theta));
+        theta = read<uint64_t>(is);
       }
     }
   }
@@ -442,17 +435,16 @@
   const char* ptr = static_cast<const char*>(bytes);
   const char* base = ptr;
   uint8_t preamble_longs;
-  ptr += copy_from_mem(ptr, &preamble_longs, sizeof(preamble_longs));
+  ptr += copy_from_mem(ptr, preamble_longs);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(serial_version));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t type;
-  ptr += copy_from_mem(ptr, &type, sizeof(type));
-  uint16_t unused16;
-  ptr += copy_from_mem(ptr, &unused16, sizeof(unused16));
+  ptr += copy_from_mem(ptr, type);
+  ptr += sizeof(uint16_t); // unused
   uint8_t flags_byte;
-  ptr += copy_from_mem(ptr, &flags_byte, sizeof(flags_byte));
+  ptr += copy_from_mem(ptr, flags_byte);
   uint16_t seed_hash;
-  ptr += copy_from_mem(ptr, &seed_hash, sizeof(seed_hash));
+  ptr += copy_from_mem(ptr, seed_hash);
   checker<true>::check_sketch_type(type, SKETCH_TYPE);
   checker<true>::check_serial_version(serial_version, SERIAL_VERSION);
   const bool is_empty = flags_byte & (1 << flags::IS_EMPTY);
@@ -465,12 +457,11 @@
       num_entries = 1;
     } else {
       ensure_minimum_memory(size, 8); // read the first prelong before this method
-      ptr += copy_from_mem(ptr, &num_entries, sizeof(num_entries));
-      uint32_t unused32;
-      ptr += copy_from_mem(ptr, &unused32, sizeof(unused32));
+      ptr += copy_from_mem(ptr, num_entries);
+      ptr += sizeof(uint32_t); // unused
       if (preamble_longs > 2) {
         ensure_minimum_memory(size, (preamble_longs - 1) << 3);
-        ptr += copy_from_mem(ptr, &theta, sizeof(theta));
+        ptr += copy_from_mem(ptr, theta);
       }
     }
   }
diff --git a/tuple/include/tuple_sketch_impl.hpp b/tuple/include/tuple_sketch_impl.hpp
index 51d79ad..f5b88b5 100644
--- a/tuple/include/tuple_sketch_impl.hpp
+++ b/tuple/include/tuple_sketch_impl.hpp
@@ -347,36 +347,36 @@
 void compact_tuple_sketch<S, A>::serialize(std::ostream& os, const SerDe& sd) const {
   const bool is_single_item = entries_.size() == 1 && !this->is_estimation_mode();
   const uint8_t preamble_longs = this->is_empty() || is_single_item ? 1 : this->is_estimation_mode() ? 3 : 2;
-  os.write(reinterpret_cast<const char*>(&preamble_longs), sizeof(preamble_longs));
+  write(os, preamble_longs);
   const uint8_t serial_version = SERIAL_VERSION;
-  os.write(reinterpret_cast<const char*>(&serial_version), sizeof(serial_version));
+  write(os, serial_version);
   const uint8_t family = SKETCH_FAMILY;
-  os.write(reinterpret_cast<const char*>(&family), sizeof(family));
+  write(os, family);
   const uint8_t type = SKETCH_TYPE;
-  os.write(reinterpret_cast<const char*>(&type), sizeof(type));
+  write(os, type);
   const uint8_t unused8 = 0;
-  os.write(reinterpret_cast<const char*>(&unused8), sizeof(unused8));
+  write(os, unused8);
   const uint8_t flags_byte(
     (1 << flags::IS_COMPACT) |
     (1 << flags::IS_READ_ONLY) |
     (this->is_empty() ? 1 << flags::IS_EMPTY : 0) |
     (this->is_ordered() ? 1 << flags::IS_ORDERED : 0)
   );
-  os.write(reinterpret_cast<const char*>(&flags_byte), sizeof(flags_byte));
+  write(os, flags_byte);
   const uint16_t seed_hash = get_seed_hash();
-  os.write(reinterpret_cast<const char*>(&seed_hash), sizeof(seed_hash));
+  write(os, seed_hash);
   if (!this->is_empty()) {
     if (!is_single_item) {
       const uint32_t num_entries = entries_.size();
-      os.write(reinterpret_cast<const char*>(&num_entries), sizeof(num_entries));
+      write(os, num_entries);
       const uint32_t unused32 = 0;
-      os.write(reinterpret_cast<const char*>(&unused32), sizeof(unused32));
+      write(os, unused32);
       if (this->is_estimation_mode()) {
-        os.write(reinterpret_cast<const char*>(&(this->theta_)), sizeof(uint64_t));
+        write(os, this->theta_);
       }
     }
     for (const auto& it: entries_) {
-      os.write(reinterpret_cast<const char*>(&it.first), sizeof(uint64_t));
+      write(os, it.first);
       sd.serialize(os, &it.second, 1);
     }
   }
@@ -393,36 +393,36 @@
   uint8_t* ptr = bytes.data() + header_size_bytes;
   const uint8_t* end_ptr = ptr + size;
 
-  ptr += copy_to_mem(&preamble_longs, ptr, sizeof(preamble_longs));
+  ptr += copy_to_mem(preamble_longs, ptr);
   const uint8_t serial_version = SERIAL_VERSION;
-  ptr += copy_to_mem(&serial_version, ptr, sizeof(serial_version));
+  ptr += copy_to_mem(serial_version, ptr);
   const uint8_t family = SKETCH_FAMILY;
-  ptr += copy_to_mem(&family, ptr, sizeof(family));
+  ptr += copy_to_mem(family, ptr);
   const uint8_t type = SKETCH_TYPE;
-  ptr += copy_to_mem(&type, ptr, sizeof(type));
+  ptr += copy_to_mem(type, ptr);
   const uint8_t unused8 = 0;
-  ptr += copy_to_mem(&unused8, ptr, sizeof(unused8));
+  ptr += copy_to_mem(unused8, ptr);
   const uint8_t flags_byte(
     (1 << flags::IS_COMPACT) |
     (1 << flags::IS_READ_ONLY) |
     (this->is_empty() ? 1 << flags::IS_EMPTY : 0) |
     (this->is_ordered() ? 1 << flags::IS_ORDERED : 0)
   );
-  ptr += copy_to_mem(&flags_byte, ptr, sizeof(flags_byte));
+  ptr += copy_to_mem(flags_byte, ptr);
   const uint16_t seed_hash = get_seed_hash();
-  ptr += copy_to_mem(&seed_hash, ptr, sizeof(seed_hash));
+  ptr += copy_to_mem(seed_hash, ptr);
   if (!this->is_empty()) {
     if (!is_single_item) {
       const uint32_t num_entries = entries_.size();
-      ptr += copy_to_mem(&num_entries, ptr, sizeof(num_entries));
+      ptr += copy_to_mem(num_entries, ptr);
       const uint32_t unused32 = 0;
-      ptr += copy_to_mem(&unused32, ptr, sizeof(unused32));
+      ptr += copy_to_mem(unused32, ptr);
       if (this->is_estimation_mode()) {
-        ptr += copy_to_mem(&theta_, ptr, sizeof(uint64_t));
+        ptr += copy_to_mem(theta_, ptr);
       }
     }
     for (const auto& it: entries_) {
-      ptr += copy_to_mem(&it.first, ptr, sizeof(uint64_t));
+      ptr += copy_to_mem(it.first, ptr);
       ptr += sd.serialize(ptr, end_ptr - ptr, &it.second, 1);
     }
   }
@@ -432,20 +432,13 @@
 template<typename S, typename A>
 template<typename SerDe>
 compact_tuple_sketch<S, A> compact_tuple_sketch<S, A>::deserialize(std::istream& is, uint64_t seed, const SerDe& sd, const A& allocator) {
-  uint8_t preamble_longs;
-  is.read(reinterpret_cast<char*>(&preamble_longs), sizeof(preamble_longs));
-  uint8_t serial_version;
-  is.read(reinterpret_cast<char*>(&serial_version), sizeof(serial_version));
-  uint8_t family;
-  is.read(reinterpret_cast<char*>(&family), sizeof(family));
-  uint8_t type;
-  is.read(reinterpret_cast<char*>(&type), sizeof(type));
-  uint8_t unused8;
-  is.read(reinterpret_cast<char*>(&unused8), sizeof(unused8));
-  uint8_t flags_byte;
-  is.read(reinterpret_cast<char*>(&flags_byte), sizeof(flags_byte));
-  uint16_t seed_hash;
-  is.read(reinterpret_cast<char*>(&seed_hash), sizeof(seed_hash));
+  const auto preamble_longs = read<uint8_t>(is);
+  const auto serial_version = read<uint8_t>(is);
+  const auto family = read<uint8_t>(is);
+  const auto type = read<uint8_t>(is);
+  read<uint8_t>(is); // unused
+  const auto flags_byte = read<uint8_t>(is);
+  const auto seed_hash = read<uint16_t>(is);
   checker<true>::check_serial_version(serial_version, SERIAL_VERSION);
   checker<true>::check_sketch_family(family, SKETCH_FAMILY);
   checker<true>::check_sketch_type(type, SKETCH_TYPE);
@@ -458,11 +451,10 @@
     if (preamble_longs == 1) {
       num_entries = 1;
     } else {
-      is.read(reinterpret_cast<char*>(&num_entries), sizeof(num_entries));
-      uint32_t unused32;
-      is.read(reinterpret_cast<char*>(&unused32), sizeof(unused32));
+      num_entries = read<uint32_t>(is);
+      read<uint32_t>(is); // unused
       if (preamble_longs > 2) {
-        is.read(reinterpret_cast<char*>(&theta), sizeof(theta));
+        theta = read<uint64_t>(is);
       }
     }
   }
@@ -472,8 +464,7 @@
     entries.reserve(num_entries);
     std::unique_ptr<S, deleter_of_summaries> summary(alloc.allocate(1), deleter_of_summaries(1, false, allocator));
     for (size_t i = 0; i < num_entries; ++i) {
-      uint64_t key;
-      is.read(reinterpret_cast<char*>(&key), sizeof(uint64_t));
+      const auto key = read<uint64_t>(is);
       sd.deserialize(is, summary.get(), 1);
       entries.push_back(Entry(key, std::move(*summary)));
       (*summary).~S();
@@ -491,19 +482,18 @@
   const char* ptr = static_cast<const char*>(bytes);
   const char* base = ptr;
   uint8_t preamble_longs;
-  ptr += copy_from_mem(ptr, &preamble_longs, sizeof(preamble_longs));
+  ptr += copy_from_mem(ptr, preamble_longs);
   uint8_t serial_version;
-  ptr += copy_from_mem(ptr, &serial_version, sizeof(serial_version));
+  ptr += copy_from_mem(ptr, serial_version);
   uint8_t family;
-  ptr += copy_from_mem(ptr, &family, sizeof(family));
+  ptr += copy_from_mem(ptr, family);
   uint8_t type;
-  ptr += copy_from_mem(ptr, &type, sizeof(type));
-  uint8_t unused8;
-  ptr += copy_from_mem(ptr, &unused8, sizeof(unused8));
+  ptr += copy_from_mem(ptr, type);
+  ptr += sizeof(uint8_t); // unused
   uint8_t flags_byte;
-  ptr += copy_from_mem(ptr, &flags_byte, sizeof(flags_byte));
+  ptr += copy_from_mem(ptr, flags_byte);
   uint16_t seed_hash;
-  ptr += copy_from_mem(ptr, &seed_hash, sizeof(seed_hash));
+  ptr += copy_from_mem(ptr, seed_hash);
   checker<true>::check_serial_version(serial_version, SERIAL_VERSION);
   checker<true>::check_sketch_family(family, SKETCH_FAMILY);
   checker<true>::check_sketch_type(type, SKETCH_TYPE);
@@ -518,12 +508,11 @@
       num_entries = 1;
     } else {
       ensure_minimum_memory(size, 8); // read the first prelong before this method
-      ptr += copy_from_mem(ptr, &num_entries, sizeof(num_entries));
-      uint32_t unused32;
-      ptr += copy_from_mem(ptr, &unused32, sizeof(unused32));
+      ptr += copy_from_mem(ptr, num_entries);
+      ptr += sizeof(uint32_t); // unused
       if (preamble_longs > 2) {
         ensure_minimum_memory(size, (preamble_longs - 1) << 3);
-        ptr += copy_from_mem(ptr, &theta, sizeof(theta));
+        ptr += copy_from_mem(ptr, theta);
       }
     }
   }
@@ -536,7 +525,7 @@
     std::unique_ptr<S, deleter_of_summaries> summary(alloc.allocate(1), deleter_of_summaries(1, false, allocator));
     for (size_t i = 0; i < num_entries; ++i) {
       uint64_t key;
-      ptr += copy_from_mem(ptr, &key, sizeof(key));
+      ptr += copy_from_mem(ptr, key);
       ptr += sd.deserialize(ptr, base + size - ptr, summary.get(), 1);
       entries.push_back(Entry(key, std::move(*summary)));
       (*summary).~S();