C/C++ 宏根据类型定义定义类型

C/C++ macro define type from defines of types

我想像这样在另一个定义中扩展一个定义:

#define SCALAR_TYPES uint8_t, int8_t, uint16_t, int16_t, double, string
#define VECTOR_TYPES vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>

#define VARIANT_TYPENAMES SCALAR_TYPES, VECTOR_TYPES

这样 VARIANT_TYPENAMES 将扩展为包含两个先前定义的宏的串联的列表。 使用 -E 标志表明宏没有在 VARIANT_TYPENAMES 内部扩展,我需要以这种方式定义它们,因为 VECTOR_TYPES 将使用类似于描述的内容从 SCALAR_TYPES 生成 [=13] =].

有没有办法强制预处理器在宏中扩展宏?

编辑

澄清一下我的意图。我正在尝试实现一个通用的数据容器,可以从生成的 matlab 算法的 C 代码中使用。与此容器的交互必须使用仅 C 接口完成,因为生成的代码仅是 C,但接口背后的代码我可以使用 C++ 代码。

这个容器可以容纳不同的标量类型和这些标量的向量,这可以使用 std::variant.

在 C 接口方面,我必须提供函数来获取和放置数据 from/in 容器,如下所示:

extern "C"{
int getter_for_int(const char *key);
void put_for_intt(const char * key, int val);
void put_for_int_array(const char * key, int *val, uint32_t size);
}

这些只是其中的一小部分,但是所有这些 getter 和 put-ers 的实现从一种数据类型到另一种数据类型都非常相似,如果我可以让预处理器在内部扩展宏,那么这些函数可以由预处理器生成#定义.

您的问题不完整,因为包含的宏正在运行:

#include <iostream>

#define SCALAR_TYPES uint8_t, int8_t, uint16_t, int16_t, double, string
#define VECTOR_TYPES vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>

#define VARIANT_TYPENAMES SCALAR_TYPES, VECTOR_TYPES

#define STR(X) STRINGIFY(X)
#define STRINGIFY(X) #X

int main()
{
    std::cout << STR((SCALAR_TYPES)) << '\n';
    std::cout << STR((VECTOR_TYPES)) << '\n';
    std::cout << STR((VARIANT_TYPENAMES)) << '\n';
    return 0;
}

产生输出:

(uint8_t, int8_t, uint16_t, int16_t, double, string)
(vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>)
(uint8_t, int8_t, uint16_t, int16_t, double, string, vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>)

https://wandbox.org/permlink/1JJE838O2CNOOpuT
在这里你可以找到 used macros explanation.

另外请避免使用宏。这应该是最后的手段,因为它有很多缺点。

如果您不介意巴洛克语法,您可以使用 X Macro 实现您想要的。创建两个单独的标量和矢量类型列表:

#define SCALAR_TYPES(_)     \
    _(u32,      uint32_t)   \
    _(double,   double)     \
    _(cstr,     char *)

#define VECTOR_TYPES(_)     \
    _(u32,      uint32_t)   \
    _(double,   double)     \
    _(cstr,     char *)

这两个宏是"gerenator macros"。他们将另一个宏 _ 作为参数。该宏必须有两个参数,用于创建合适的函数名称的 NAME 和描述标量类型或数组元素类型的 TYPE

要创建示例中的界面,首先创建所需的宏:

#define SCALAR_GET(N, T) T get_##N(const char *key);
#define VECTOR_GET(N, T) T *get_##N##_array(const char *key);
#define SCALAR_PUT(N, T) void put_##N(const char * key, T val);
#define VECTOR_PUT(N, T) void put_##N##_array(const char * key, T *val, uint32_t size);

然后将它们传递给两个生成器宏:

extern "C"{
    SCALAR_TYPES(SCALAR_GET)
    SCALAR_TYPES(SCALAR_PUT)
    VECTOR_TYPES(VECTOR_GET)
    VECTOR_TYPES(VECTOR_PUT)
}

这将产生:

extern "C" {
    uint32_t get_u32(const char *key);
    double get_double(const char *key);
    char *get_cstr(const char *key);
    void put_u32(const char *key, uint32_t val);
    void put_double(const char *key, double val);
    void put_cstr(const char *key, char *val);
    uint32_t *get_u32_array(const char *key);
    double *get_double_array(const char *key);
    char **get_cstr_array(const char *key);
    void put_u32_array(const char *key, uint32_t * val, uint32_t size);
    void put_double_array(const char *key, double *val, uint32_t size);
    void put_cstr_array(const char *key, char **val, uint32_t size);
}

要获取 std::variant 的列表,请使用:

#define SCALAR_COMMA(N, T) T,
#define VECTOR_COMMA(N, T) T *,

#define VARIANT_TYPENAMES \
    SCALAR_TYPES(SCALAR_COMMA) VECTOR_TYPES(VECTOR_COMMA)

不过有一个问题:VARIANT_TYPPENAMES 会产生尾随逗号。在数组初始值设定项中,允许使用尾随逗号。在enums中,可以在最后一个枚举值后定义一个"max"值

但是还有一个宏解决方案,显示在 post 的末尾。


您还可以在 gererator 宏中包含数据的 "class" – 标量或向量 &ndash。

#define TYPES(_)                            \
    _(SCALAR,   u32,            uint32_t)   \
    _(SCALAR,   double,         double)     \
    _(SCALAR,   cstr,           char *)     \
    _(VECTOR,   u32_array,      uint32_t)   \
    _(VECTOR,   double_array,   double)     \
    _(VECTOR,   cstr_array,     char *)

他们定义了一组以 SCALAR_ 或 ´VECTOR_` 作为前缀的宏,这样您就可以使用标记粘贴来创建他们的名字:

#define SCALAR_TYPE(T) T
#define VECTOR_TYPE(T) T *

#define SCALAR_ARG(T) T val
#define VECTOR_ARG(T) T* val, uint32_t size

现在你的马罗斯看起来像:

#define GET(C, N, T) C##_TYPE(T) get_##N(const char *key);
#define PUT(C, N, T) void put_##N(const char * key, C##_ARG(T));

extern "C"{
    TYPES(GET)
    TYPES(PUT)
}

#define COMMA(C, N, T) C##_TYPE(T),

#define VARIANT_TYPENAMES TYPES(COMMA)

它们产生与上面相同的结果。


最后,关于 VARIANT_TYPENAMES 中的尾随逗号:您可以通过将每个宏中的尾随逗号转换为前导逗号然后丢弃开头的逗号来摆脱逗号。

#define COMMA(C, N, T) , C##_TYPE(T)

#define TAIL_(A, ...) __VA_ARGS__
#define TAIL(...) TAIL_(__VA_ARGS__)

#define VARIANT_TYPENAMES TAIL(TYPES(COMMA))

这个可行,因为宏参数可以为空,但需要两步扩展才能将 TAIL(TYPES(COMMA)) 变成 TAIL_(, T1, T2, T3, ...)


这个解决方案需要一些时间才能开始工作,尤其是因为展开的宏缩减了它们的空白并且可读性不强,但是一旦你有了你的系统,你就可以轻松地添加新类型。

适用于使用宏的常见注意事项。我还想向您指出另一种可能的解决方案:编写一个脚本或程序,根据比 X 宏更好的定义为您生成接口,并将其包含在您的构建过程中。