有条件地初始化结构字段 - 仅当它存在于该结构中时
Conditionally initialize a struct field - only if it exists in that struct
正在为一个非常简单的单元测试框架工作 API:
extern "C" void execute_command(int cmd, void *params);
根据 cmd
参数将 params
转换为适当的结构。我无法更改该接口,也无法修改指定命令和不同参数结构(均为 POD)的 header。
我确实可以访问类似以下内容的数组:
{ 0 /*cmd number*/, "PARAM_STRUCT_FOR_CMD_0", sizeof(PARAM_STRUCT_FOR_CMD_0) }
这些参数结构有一些共同的属性。例如,它们中的许多都有一个像 void *pObject;
这样的字段,尽管它并不总是相同的偏移量。为了说明,假设有三种结构:
struct {
void *pObject;
int someData;
} PARAM_STRUCT_FOR_CMD_0;
struct {
float someFloatData;
void *pObject;
} PARAM_STRUCT_FOR_CMD_1;
struct {
float someFloatData;
void *pAnotherObject;
} PARAM_STRUCT_FOR_CMD_2;
这两个pObject
字段代表同一事物,而pAnotherObject
是不相关的。
现在,谈谈我真正想要的:我想根据 cmd
将 void*
转换为某个结构,并设置它的 pObject
字段,如果它存在于该结构中。理想情况下,我可以做类似的事情:
void *pGlobalObject;
void execcmd(int cmd)
{
static uint8_t params[MAX_SIZE_OF_PARAM_STRUCT];
memset(params, 0, MAX_SIZE_OF_PARAM_STRUCT);
INIT_STRUCT_IF_POSSIBLE(cmd, (void*)params);
execute_command(cmd, params);
}
其中 INIT_STRUCT_IF_POSSIBLE
可能是这样的:
#define INIT_STRUCT_IF_POSSIBLE(cmd, str) \
do { \
switch (cmd) \
{ \
case 0: static_cast<PARAM_STRUCT_FOR_CMD_0*>(str)->pObject = pGlobalObject; break; \
case 1: static_cast<PARAM_STRUCT_FOR_CMD_1*>(str)->pObject = pGlobalObject; break; \
case 2: /* nothing, no pObject field */ break; \
} \
} while (0)
除非它不是真正可扩展的。我有大约 1000 个可能的命令,假设我想设置 5 个字段(没有结构包含全部 5 个),并且可以添加新命令,所以我想避免手动更改它。
显而易见的解决方案是一个额外的构建步骤,它解析所有结构,并创建它们的初始值设定项。但是,由于项目的结构,添加这个额外的构建步骤很痛苦,所以我希望有一个纯 C++ 解决方案。
如果有一种方法可以使用 C 预处理器生成初始值设定项,我完全赞成。如果它能以某种方式使用模板来完成,那也一样好。如果有帮助,我有可用的 boost 和 C++11。
可以解决这个问题的一个方法是指定初始值设定项,例如 STR x = {.pObject = pGlobalObject; };
。不幸的是,当该字段不可用时,它们会导致错误。有什么办法可以忽略不存在的字段吗? (是的,我知道它们只是 C,不是 C++,但如果需要我可以切换到 C)
使用某些 SFINAE,您可以相应地检测成员和(类型)调度分配:
#include <iostream>
#include <type_traits>
// Member variable detection
// =========================
template<typename T, typename = void>
struct has_pObject : std::false_type { };
template<typename T>
struct has_pObject<T, decltype(std::declval<T>().pObject, void())> : std::true_type { };
// Optional member variable assignment
// ===================================
namespace Detail
{
template <typename T>
void assign_pObject(T& object, void* p, std::false_type) {}
template <typename T>
void assign_pObject(T& object, void* p, std::true_type) {
object.pObject = p;
}
}
template <typename T>
void assign_pObject(T& object, void* p) {
Detail::assign_pObject(object, p, has_pObject<T>());
}
// Test
// ====
struct {
void *pObject = nullptr;
int someData = 0;
} PARAM_STRUCT_FOR_CMD_0;
struct {
float someFloatData = 0;
void *pObject = nullptr;
} PARAM_STRUCT_FOR_CMD_1;
struct {
float someFloatData = 0;
void *pAnotherObject = nullptr;
} PARAM_STRUCT_FOR_CMD_2;
int main()
{
int object;
assign_pObject(PARAM_STRUCT_FOR_CMD_0, &object);
assign_pObject(PARAM_STRUCT_FOR_CMD_1, &object);
assign_pObject(PARAM_STRUCT_FOR_CMD_2, &object);
std::cout << PARAM_STRUCT_FOR_CMD_0.pObject << '\n';
std::cout << PARAM_STRUCT_FOR_CMD_1.pObject << '\n';
std::cout << PARAM_STRUCT_FOR_CMD_2.pAnotherObject << '\n';
return 0;
}
欢迎来到 SFINAE 的世界
template<typename T>
typename std::enable_if<
std::is_same<decltype(T::pObject), void*>::value
>::type setPobject(T *t) {
t->pObject = pGlobalObject;
}
void setPobject(void *t) { }
template<typename T>
typename std::enable_if<
std::is_same<decltype(T::someFloatData), float>::value
>::type setSomeFloatData(T *t) {
t->someFloatData = someGlobalFloat;
}
void setSomeFloatData(void *t) { }
// ...
只需为所有具有正确类型的对象调用它们,它们就会自行判断它们是否适用。您还可以自动转换
template<typename D>
struct Call {
static void call(void *t) {
setPobject(static_cast<D*>(t));
setSomeFloatData(static_cast<D*>(t));
}
};
// desginated initializers here for convenience (non-C++)
void (* const table[])(void*) = {
[0] = Call<PARAM_STRUCT_FOR_CMD_0>::call,
[1] = Call<PARAM_STRUCT_FOR_CMD_1>::call
// ...
};
正在为一个非常简单的单元测试框架工作 API:
extern "C" void execute_command(int cmd, void *params);
根据 cmd
参数将 params
转换为适当的结构。我无法更改该接口,也无法修改指定命令和不同参数结构(均为 POD)的 header。
我确实可以访问类似以下内容的数组:
{ 0 /*cmd number*/, "PARAM_STRUCT_FOR_CMD_0", sizeof(PARAM_STRUCT_FOR_CMD_0) }
这些参数结构有一些共同的属性。例如,它们中的许多都有一个像 void *pObject;
这样的字段,尽管它并不总是相同的偏移量。为了说明,假设有三种结构:
struct {
void *pObject;
int someData;
} PARAM_STRUCT_FOR_CMD_0;
struct {
float someFloatData;
void *pObject;
} PARAM_STRUCT_FOR_CMD_1;
struct {
float someFloatData;
void *pAnotherObject;
} PARAM_STRUCT_FOR_CMD_2;
这两个pObject
字段代表同一事物,而pAnotherObject
是不相关的。
现在,谈谈我真正想要的:我想根据 cmd
将 void*
转换为某个结构,并设置它的 pObject
字段,如果它存在于该结构中。理想情况下,我可以做类似的事情:
void *pGlobalObject;
void execcmd(int cmd)
{
static uint8_t params[MAX_SIZE_OF_PARAM_STRUCT];
memset(params, 0, MAX_SIZE_OF_PARAM_STRUCT);
INIT_STRUCT_IF_POSSIBLE(cmd, (void*)params);
execute_command(cmd, params);
}
其中 INIT_STRUCT_IF_POSSIBLE
可能是这样的:
#define INIT_STRUCT_IF_POSSIBLE(cmd, str) \
do { \
switch (cmd) \
{ \
case 0: static_cast<PARAM_STRUCT_FOR_CMD_0*>(str)->pObject = pGlobalObject; break; \
case 1: static_cast<PARAM_STRUCT_FOR_CMD_1*>(str)->pObject = pGlobalObject; break; \
case 2: /* nothing, no pObject field */ break; \
} \
} while (0)
除非它不是真正可扩展的。我有大约 1000 个可能的命令,假设我想设置 5 个字段(没有结构包含全部 5 个),并且可以添加新命令,所以我想避免手动更改它。
显而易见的解决方案是一个额外的构建步骤,它解析所有结构,并创建它们的初始值设定项。但是,由于项目的结构,添加这个额外的构建步骤很痛苦,所以我希望有一个纯 C++ 解决方案。
如果有一种方法可以使用 C 预处理器生成初始值设定项,我完全赞成。如果它能以某种方式使用模板来完成,那也一样好。如果有帮助,我有可用的 boost 和 C++11。
可以解决这个问题的一个方法是指定初始值设定项,例如 STR x = {.pObject = pGlobalObject; };
。不幸的是,当该字段不可用时,它们会导致错误。有什么办法可以忽略不存在的字段吗? (是的,我知道它们只是 C,不是 C++,但如果需要我可以切换到 C)
使用某些 SFINAE,您可以相应地检测成员和(类型)调度分配:
#include <iostream>
#include <type_traits>
// Member variable detection
// =========================
template<typename T, typename = void>
struct has_pObject : std::false_type { };
template<typename T>
struct has_pObject<T, decltype(std::declval<T>().pObject, void())> : std::true_type { };
// Optional member variable assignment
// ===================================
namespace Detail
{
template <typename T>
void assign_pObject(T& object, void* p, std::false_type) {}
template <typename T>
void assign_pObject(T& object, void* p, std::true_type) {
object.pObject = p;
}
}
template <typename T>
void assign_pObject(T& object, void* p) {
Detail::assign_pObject(object, p, has_pObject<T>());
}
// Test
// ====
struct {
void *pObject = nullptr;
int someData = 0;
} PARAM_STRUCT_FOR_CMD_0;
struct {
float someFloatData = 0;
void *pObject = nullptr;
} PARAM_STRUCT_FOR_CMD_1;
struct {
float someFloatData = 0;
void *pAnotherObject = nullptr;
} PARAM_STRUCT_FOR_CMD_2;
int main()
{
int object;
assign_pObject(PARAM_STRUCT_FOR_CMD_0, &object);
assign_pObject(PARAM_STRUCT_FOR_CMD_1, &object);
assign_pObject(PARAM_STRUCT_FOR_CMD_2, &object);
std::cout << PARAM_STRUCT_FOR_CMD_0.pObject << '\n';
std::cout << PARAM_STRUCT_FOR_CMD_1.pObject << '\n';
std::cout << PARAM_STRUCT_FOR_CMD_2.pAnotherObject << '\n';
return 0;
}
欢迎来到 SFINAE 的世界
template<typename T>
typename std::enable_if<
std::is_same<decltype(T::pObject), void*>::value
>::type setPobject(T *t) {
t->pObject = pGlobalObject;
}
void setPobject(void *t) { }
template<typename T>
typename std::enable_if<
std::is_same<decltype(T::someFloatData), float>::value
>::type setSomeFloatData(T *t) {
t->someFloatData = someGlobalFloat;
}
void setSomeFloatData(void *t) { }
// ...
只需为所有具有正确类型的对象调用它们,它们就会自行判断它们是否适用。您还可以自动转换
template<typename D>
struct Call {
static void call(void *t) {
setPobject(static_cast<D*>(t));
setSomeFloatData(static_cast<D*>(t));
}
};
// desginated initializers here for convenience (non-C++)
void (* const table[])(void*) = {
[0] = Call<PARAM_STRUCT_FOR_CMD_0>::call,
[1] = Call<PARAM_STRUCT_FOR_CMD_1>::call
// ...
};