您可以在编译时执行此操作,而无需触及原始结构的源代码:
#include <iostream>
#include <limits>
#include <memory.h>
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 <typename T>
inline
void assign(T & dst, T const & src) {
dst = src;
}
template <size_t N>
inline
void assign(char (&dst)[N], char const (&src)[N]) {
memcpy(dst, src, N);
}
#define DEFINE_ENTRY_FIELD_COPIER(field) \
template <typename T> \
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 <typename T>
Entry gatherFrom(T const & t) {
Entry e = {"", -1, std::numeric_limits<float>::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";
}
该DEFINE_ENTRY_FIELD_COPIER()
宏为您要提取的每个字段定义了一对重载函数。一个重载 ( copy_##field(T const * t, …)
, 变成copy_name(T const * t, …)
,copy_index(T const * t, …)
等) 将其返回类型定义为,如果有一个名为,的数据成员decltype(T::field, true)
, 则解析为 type 。如果没有这样的字段, 替换失败, 但不会导致编译-time 错误,第一个重载被简单地视为它不存在(这称为SFINAE),因此调用解析为第二个重载 ,它的第一个参数完全接受任何类型并且什么都不做。bool
T
name
index
T
copy_##field(void const * t, …)
笔记:
因为此代码在编译时解决了重载,gatherFrom()
所以是最佳的,gatherFrom<A>()
例如,生成的二进制代码看起来就像您手动调整它一样A
:
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;
}
在 g++ 4.8 下,使用-O3
,gatherFrom<A>()
并handCraftedGatherFromA()
生成相同的代码:
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 的gatherFrom<A>()
表现不佳;它冗余地对整个条目进行零初始化。所以我猜这不全是玫瑰。
通过使用NRVOe
,两个版本在返回时都避免了复制。movq %rcx, %rax
但是,我应该注意,两个版本都将通过使用输出参数而不是返回值来保存一个操作码 ( )。
这些copy_…()
函数返回一个bool
结果,指示复制是否发生。这目前没有使用,但它可以用于,例如,定义int Entry::validFields
为指示填充了哪些字段的位掩码。
不需要宏;它只是为了DRY。基本成分是使用 SFINAE。
assign()
也不需要重载。他们只是避免使用不同的几乎相同的宏来处理 char 数组。
上面的代码依赖于 C++11 的 decltype 关键字。如果您使用的是较旧的编译器,它会更混乱,但仍然可能。我设法想出的最干净的解决方案如下。它符合 C++98 标准并且仍然基于 SFINAE 原则:
template <typename C, typename F, F (C::*), typename T>
struct EnableCopy {
typedef T type;
};
#define DEFINE_ENTRY_FIELD_COPIER(field, ftype) \
template <typename T> \
inline \
typename EnableCopy<T, ftype, &T::field, bool>::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);
您还必须放弃 C++11 的可移植性std::numeric_limits<float>::quiet_NaN()
并使用一些技巧(0.0f/0.0f
似乎有效)或选择另一个神奇的值。