I\'m doing some work in C++ for a company that has everything else written in C (using C isn\'t an option for me :( ). They have a number of data structures that are VERY simila
You can do this at compile-time without touching the source of the original structs:
#include
#include
#include
struct A
{
char name[256];
int index;
float percision;
};
struct B
{
int index;
char name[256];
int latency;
};
struct Entry
{
char name[256];
int index;
float percision;
int latency;
/* more fields that are specific to only 1 or more structure */
};
inline
std::ostream & operator<<(std::ostream & os, Entry const & e) {
return os << e.name << "{" << e.index << ", " << e.percision << ", " << e.latency << "}";
}
template
inline
void assign(T & dst, T const & src) {
dst = src;
}
template
inline
void assign(char (&dst)[N], char const (&src)[N]) {
memcpy(dst, src, N);
}
#define DEFINE_ENTRY_FIELD_COPIER(field) \
template \
inline \
decltype(T::field, true) copy_##field(T const * t, Entry & e) { \
assign(e.field, t->field); \
return true; \
} \
\
inline \
bool copy_##field(void const *, Entry &) { \
return false; \
}
DEFINE_ENTRY_FIELD_COPIER(name)
DEFINE_ENTRY_FIELD_COPIER(index)
DEFINE_ENTRY_FIELD_COPIER(percision)
DEFINE_ENTRY_FIELD_COPIER(latency)
template
Entry gatherFrom(T const & t) {
Entry e = {"", -1, std::numeric_limits::quiet_NaN(), -1};
copy_name(&t, e);
copy_index(&t, e);
copy_percision(&t, e);
copy_latency(&t, e);
return e;
}
int main() {
A a = {"Foo", 12, 1.2};
B b = {23, "Bar", 34};
std::cout << "a = " << gatherFrom(a) << "\n";
std::cout << "b = " << gatherFrom(b) << "\n";
}
The DEFINE_ENTRY_FIELD_COPIER()
macro defines a pair of overloaded functions for each field you want to extract. One overload (copy_##field(T const * t, …)
, which becomes copy_name(T const * t, …)
, copy_index(T const * t, …)
, etc.) defines its return type as decltype(T::field, true)
, which resolves to type bool
if T
has a data member called name
, index
, etc. If T
doesn't have such a field, the substitution fails, but rather than causing a compile-time error, this first overload is simply treated as if it doesn't exist (this is called SFINAE) and the call thus resolves to the second overload, copy_##field(void const * t, …)
, which accepts any type at all for its first argument and does nothing.
Notes:
Because this code resolves the overloads at compile-time, gatherFrom()
is optimal, in the sense that the generated binary code for gatherFrom()
, for example, will look as if you tuned it for A
by hand:
Entry handCraftedGatherFromA(A const & a) {
Entry e;
e.latency = -1;
memcpy(_result.name, a.name, sizeof(a.name));
e.index = a.index;
e.percision = a.percision;
return e;
}
Under g++ 4.8 with -O3
, gatherFrom()
and handCraftedGatherFromA()
generate identical code:
pushq %rbx
movl $256, %edx
movq %rsi, %rbx
movl $-1, 264(%rdi)
call _memcpy
movss 260(%rbx), %xmm0
movq %rax, %rcx
movl 256(%rbx), %eax
movss %xmm0, 260(%rcx)
movl %eax, 256(%rcx)
movq %rcx, %rax
popq %rbx
ret
Clang 4.2's gatherFrom()
doesn't do as well, unfortunately; it redundantly zero-initialises the entire Entry. So it's not all roses, I guess.
By using NRVO, both versions avoid copying e
when returning it. However, I should note that both versions would save one op-code (movq %rcx, %rax
) by using an output parameter instead of a return value.
The copy_…()
functions return a bool
result indicating whether the copy happened or not. This isn't currently used, but it could be used, e.g., to define int Entry::validFields
as a bitmask indicating which fields were populated.
The macro isn't required; it's just for DRY. The essential ingredient is the use of SFINAE.
The assign()
overloads also aren't required. They just avoid having a different almost-identical macro to handle char arrays.
The above code relies on C++11's decltype keyword. If you are using an older compiler, it's messier, but still possible. The cleanest solution I've managed to come up with is the following. Its C++98-conformant and still based on the SFINAE principle:
template
struct EnableCopy {
typedef T type;
};
#define DEFINE_ENTRY_FIELD_COPIER(field, ftype) \
template \
inline \
typename EnableCopy::type \
copy_##field(T const * t, Entry & e) { \
copy_value(e.field, t->field); \
return true; \
} \
\
inline \
bool copy_##field(void const *, Entry &) { \
return false; \
}
DEFINE_ENTRY_FIELD_COPIER(name , char[256]);
DEFINE_ENTRY_FIELD_COPIER(index , int);
DEFINE_ENTRY_FIELD_COPIER(percision, float);
DEFINE_ENTRY_FIELD_COPIER(latency , int);
You'll also have to forgo C++11's portable std::numeric_limits
and use some trick (0.0f/0.0f
seems to work) or choose another magic value.