Access an old struct as CPP class

109 Views Asked by At

I have a question about C++ standard, I know a class is compatible with C if does not have any methods, but I'd need to have some getters (mainly not to forget various htoi and friends)

This is an example: is a class like

// declared in .hpp file
#pragma pack(1)
class c_something{
public :
  int a;
  char b[56];
  boolean c;
  WORD d;


  inline std::string getBAsString(){ return std::string(b); }
  inline uint16_t getD(){ return htons(d);}
}

compatible with the "C" type defined as a struct

// declared in an old C library header (.h)
#pragma pack(1)
typedef struct t_something{
//public :
  int a;
  char b[56];
  boolean c;
  WORD d;
} something, *pSomething;

My goal is to:

// in my new code I use the data provided by the old interface as a class
BYTE rawRecivedData rrd[56]; // a stream of data recived from the network

t_something data = (t_something) (*rrd);

printf("d value is %u", htons(data.d));

/* or just c_something *i = (c_something) (*rrd) */
c_something i = (c_something) data;

std::cout << i.std::string getBAsString() << "D:"<< i.getD() <<std::endl;
2

There are 2 best solutions below

2
Aykhan Hagverdili On

Have the C++ class wrap the C class:

class FooCpp
{
private:
    FooC m_c;

public:
    explicit FooCpp(FooC const* pc): m_c{ *c } {}

    int b() const { return htons(c.b); }
};
0
Jan Schultke On

What you're trying to do is possible in principle, but you don't have the right syntax for it.

c_something i = (c_something) data;

This line doesn't work because t_something and c_something are unrelated types. They're both trivially copyable and they both have the same subobjects, but that doesn't mean you can convert between them with a C-style cast.

How to do type-punning correctly

However, you could implicitly begin the lifetime of one c_something where c_something is located to examine it as another type with std::start_lifetime_as:

c_something* i = std::start_lifetime_as<c_something>(&data);

You could also cast between them with std::bit_cast, but that would perform run-time work:

c_something i = std::bit_cast<c_something>(data);

If you don't need both a c_something and t_something object, you can also just type-pun with std::memcpy:

c_something data;
std::memcpy(&data, raw_data, sizeof(t_something));

Other methods such as reinterpret_cast or union wouldn't be strictly correct.

Alternatives to type punning

Just because you can, doesn't mean you should. Code which does type punning is conceptually more complicated, and it's more difficult to write correctly.

You could

  • create a wrapper class that stores a reference to t_something and lets you examine it with a different interface
  • create free functions instead of getters

The latter option seems simpler.

inline std::string getBAsString(t_something& self) {
    return std::string(self.b);
}
inline uint16_t getD(t_something& self) {
    return htons(self.d);
}