Search code examples
c++c++11network-programmingbit-manipulationpacked

Packed structures and not packed without code repetition


I have this code:

// size probably 4 bytes
struct NotPacked
  {
  uint16_t first;
  uint8_t second;
  };

// size is 3 bytes
struct Packed
  {
  uint16_t first;
  uint8_t second;
  }__attribute__((packed));

I'd like to use the same structure, sometimes as packed, sometimes not. Do you know any way to write this code avoiding repetition?

[Edit] Question should have been: ".. to write this code avoiding as much code duplication as possible"

[Edit2] I've tried an experiment using empty class optimization but no success

[Edit3] Basic example added:

  Packed packet;
  receivePacketFromNetwork(&packet); // Fill the structure with data coming from the network
  NotPacked notPacked = packedToUnpacked(packet); // convert packed structure to unpacked
  processUnpacked(notPacked); // Do a lot of computations

Solution

  • You could use universal member pointers to access it.

    First, define your members in a namespace:

    namespace universal {
      template<class T, unsigned int idx=0> struct member_ptr; // TODO
      template<auto const*...> struct packed_struct; // TODO
      template<auto const*...> struct unpacked_struct; // TODO
      template<class...Ts> using variant=std::variant<Ts...>;
      template<class...Ts> struct mutable_pointer:std::variant<Ts*...>{/*TODO*/};
      template<class...Ts> using const_pointer = mutable_ptr<Ts const...>;
    }
    
    namespace Foo {
      universal::member_ptr<int16_t> first;
      universal::member_ptr<int8_t> second;
    
      using packed = universal::packed_struct< &first, &second >;
      using unpacked = universal::unpacked_struct< &first, &second >;
    
      using either = universal::variant<packed, unpacked>;
      using either_cptr = universal::const_pointer<packed, unpacked>;
      using either_mptr = universal::mutable_pointer<packed, unpacked>;
    }
    

    then you can do:

    void receivePacketFromNetwork( Foo::either_mptr ptr ) {
      assert(ptr);
      ptr->*Foo::first = 7;
      ptr->*Foo::second = 3;
    }
    

    and have it work on both types of structure.

    Writing the stuff in namespace universal isn't easy, but it isn't impossible.

    The basic idea is to overload operator->*.

    template<class T>
    struct member_ptr {
      template<class...Ts,
        std::enable_if_t< supports<Ts>() && ..., bool> = true
      >
      T& operator->*( std::variant<Ts...>& lhs, member_ptr const& self ) {
        return std::visit(
          [&self]( auto&& lhs )->T&{ return lhs->*self; },
          lhs
        );
      }
      template<class U>
      constexpr static bool supports(); //TODO
    };
    
    template<auto const* a, auto const* b, auto const*... bs>
    struct unpacked_struct<a, b, bs...>:
      unpacked_struct<a>,
      unpacked_struct<b, bs...>
    {
      using unpacked_struct<a>::operator->*;
      using unpacked_struct<b, bs...>::operator->*;
    };
    
    template<class T, , unsigned int idx, member_ptr<T, idx> const* a>
    struct unpacked_struct<a> {
      T data;
      T& operator->*( member_ptr<T, idx> const& ) & {
        return data;
      }
      T&& operator->*( member_ptr<T, idx> const& ) && {
        return std::move(data);
      }
      T const& operator->*( member_ptr<T, idx> const& ) const& {
        return data;
      }
      T const&& operator->*( member_ptr<T, idx> const& ) const&& {
        return std::move(data);
      }
    };
    

    etc.