I am trying to implement a function that sets a struct member and also serializes the data to an external output. For serialization purposes, I need to know the size and offset of the struct member, but I am struggling to obtain the offset.
In the following code, when I print the value of member, I get correct values, but I'm not able to convert it to integer.
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
struct Data {
int32_t i;
uint32_t u;
bool b;
char c;
};
Data g_data;
template <typename Type>
void set(Type Data::*member, const Type& value)
{
// Works for printing
printf("Size: %lu\tAddress = %lu\n", sizeof(Type), member);
// Invalid cast error
// printf("Size: %lu\tAddress = %lu\n", sizeof(Type), reinterpret_cast<uintptr_t>(member));
// Works, but unnecesarily complex?
// printf("Size: %lu\tAddress = %lu\n", sizeof(Type), reinterpret_cast<uintptr_t>(&(g_data.*member)) - reinterpret_cast<uintptr_t>(&g_data));
g_data.*member = value;
}
int main() {
set(&Data::i, static_cast<int32_t>(1));
set(&Data::u, static_cast<uint32_t>(2U));
set(&Data::b, true);
set(&Data::c, '1');
return 0;
}
To obtain the offset of a data member in a class type, you have two options:
offsetof
This macro expands to an integral constant expression of type
std::size_tand gives us the offset in bytes from the start of the type. This is exactly what you want.However, to use it,
setwould need to take the offset instead of the pointer to the data member:Note 1: the proper format specifier for
std::size_tis%zu, not%lu.Note 2:
offsetofrequiresDatato be a standard layout type, and writing to its bytes like this requires it to be a trivially copyable type. For a simple struct like yours, this is the case.std::bit_caststd::bit_castis a C++20 function which takes the bytes of one object, and converts it to another object with the same bytes, but different type. Since pointers to data members are typically just an offset under the hood, this would work:However, while this technically works and is closer to your original code, do not do this. It makes too many assumptions about the implementation, namely:
std::size_tWith some clever metaprogramming you could solve 2. by choosing an integer with matching size, but 1. is not solvable.