C++ variadic templates pack into std::array and unpack

Viewed 286

I want to pack variables in an array and then unpack using an interface like this

int x; float y; double z;
auto data = PackArray(x, y, z); // serializes without padding

...

int x; float y; double z;
UnpackArray(data, x, y, z); // copies data from the array to x, y, z

I tried

template<class... T>
constexpr size_t SumOfSizeofs(const T&...)
{
    return (sizeof(T) + ...);
}

// Creates array of u8 with size = sum(sizeofs)
template <class A, class ...Rest>
auto PackArray(A& a, Rest&... rest)
{
    return std::array<u8, SumOfSizeofs(a, rest...)>; // doesn't work
}

template <class Array, class...Rest>
auto UnpackArray(Array& array, Rest&... rest)
{
    // iterates through sizeof each element, copying to its new buffer from the array
}

If there was an std::packed_tuple it would be great... :( I'm bad with template syntax, help?

2 Answers

Here's my go at it:

#include <array>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <memory>
#include <type_traits>

template <class... T>
[[nodiscard]] auto PackArray(T&&... args) {
  static_assert(
      (std::is_trivially_copyable_v<std::remove_reference_t<T>> && ...),
      "Packing requires trivial-copying");
  std::array<std::byte, (sizeof(T) + ... + 0)> data;
  std::size_t offet = 0;
  (std::memcpy(data.data() + (offet += sizeof(args)) - sizeof(args),
               std::addressof(args), sizeof(args)), ...);
  return data;
}

template <std::size_t sz, class... T>
auto UnpackArray(std::array<std::byte, sz> const& data, T&... args) {
  static_assert(sz == (sizeof(T) + ... + 0), "Mismatch");
  static_assert(
      (std::is_trivially_copyable_v<std::remove_reference_t<T>> && ...),
      "UnPacking requires trivial-copying");
  std::size_t offet = 0;
  (std::memcpy(std::addressof(args),
               data.data() + (offet += sizeof(args)) - sizeof(args),
               sizeof(args)), ...);
}

int main() {
  int x = 42;
  float y = 3.14f;
  double z = 2.72;
  auto data = PackArray(x, y, z);  // serializes without padding

  int x1;
  float y1;
  double z1;
  UnpackArray(data, x1, y1, z1);

  std::printf("%d, %.2f, %.2f", x1, y1, z1);
}

Note the use of std::addressof to avoid problems with objects overloading operator&.

Here is complete code:

#include <cstddef>
#include <array>
#include <cstdint>
#include <string.h>


template <class... Args>
constexpr auto PackArray(const Args&... args)
{
    std::array<uint8_t, (0 + ... + sizeof(Args))> arr;

    size_t packPos = 0;
    auto pack = [&packPos, &arr](const auto& arg) {
         memcpy(arr.data() + packPos, &arg, sizeof(arg));
         packPos += sizeof(arg);
    };
    (pack(args), ...);
    return arr;
}

template <size_t sz, class... Args>
constexpr void UnpackArray(std::array<uint8_t, sz> arr, Args&... args)
{
    static_assert(arr.size() == (... + sizeof(Args)), "Mistmatched packed array size");
    size_t packPos = 0;
    auto unpack = [&packPos, &arr](auto& arg) {
         memcpy(&arg, arr.data() + packPos, sizeof(arg));
         packPos += sizeof(arg);
    };
    (unpack(args),...);
}

auto arr = PackArray(10, 1.0, nullptr);

int a;
double b;
void* c;

void foo() {
    UnpackArray(arr, a, b, c);
}

It is a bit tailored to the use case I had in mind, so if it lacks anything you'd want to see, let me know. Also, instead of passing arguments to unpack, I could make it returning a tuple, allowing callers to enjoy structured binding syntax, but you didn't have it in your original code, so I didn't put it there either.

Last, but not the least, you might want to pepper-spray the code with asserts on triviality of the types.

Related