有条件地初始化struct字段 - 仅当它存在于该struct中时

时间:2015-10-13 17:03:24

标签: c++ api unit-testing reflection initialization

为一个非常简单的API处理类似单元测试框架的事情:

extern "C" void execute_command(int cmd, void *params);

根据params参数将cmd强制转换为适当的结构。我无法更改该界面,也无法修改指定命令的标头和不同的参数结构(均为POD)。

我可以访问类似的数组:

{ 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则无关。

现在,关于我真正想要的内容:我想基于void*cmd投射到某个结构,并设置其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语言)

2 个答案:

答案 0 :(得分:2)

欢迎来到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
   // ...
};

答案 1 :(得分:1)

对于某些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;
}