【问题标题】:C/C++ macro define type from defines of typesC/C++ 宏从类型定义中定义类型
【发布时间】:2020-05-04 10:42:53
【问题描述】:

我想在另一个定义中扩展一个定义,如下所示:

#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 将使用类似于here 描述的东西从 SCALAR_TYPES 生成。

有没有办法强制预处理器在宏内部展开宏?

编辑

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

这个容器可以保存不同的标量类型和这些标量的向量,这可以使用 std::variant 来完成。

在 C 接口方面,我必须提供从容器中获取和放入数据的函数,如下所示:

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-er 的实现从一种数据类型到另一种数据类型都非常相似,如果我可以让预处理器在 #define 中扩展宏,则这些函数可以由预处理器生成.

【问题讨论】:

  • 不要使用宏。看起来这是std::variant 或类似的类型列表,因此您可以使用简单的typedef:using MyVariant = std::variant&lt;the,whole,list,of,types&gt;;
  • 使用#define EVAL(x) x???
  • 但是您已经在使用vector&lt;...&gt; 中的模板了。
  • 如果您想要 C++ 代码的 C API(包含在 extern "C" {} 中的东西),那么您只能使用 C 中可用的类型。仅供参考:SO: Does extern “C” force limitations on my code?
  • 也许您应该编辑您的问题以显示应该如何使用宏。目前,还不清楚——无论如何,对我来说——你真正想要实现什么。

标签: c++ c macros


【解决方案1】:

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

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

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

这两个宏是“生成器宏”。他们将另一个宏 _ 作为参数。该宏必须接受两个参数,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 中,您可以在最后一个枚举值之后定义一个“最大值”。

但也有一个宏解决方案,显示在帖子的末尾。


您还可以在 gererator 宏中包含数据的“类”——标量或向量 &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

现在你的 maros 看起来像:

#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 宏更好的定义为您生成接口,并将其包含在您的构建过程中。

【讨论】:

  • 看起来不错。你知道是否可以从宏中删除尾随逗号?
  • 我虽然不可能,但我找到了解决方案并编辑了帖子。
【解决方案2】:

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

#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

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

【讨论】:

  • 你说得对,我被以下事实误导了:使用 gcc -E 时,我在命令的输出中看不到这种扩展。知道为什么 -E 标志不能全部展开吗?
猜你喜欢
  • 2014-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-24
  • 2012-03-07
  • 1970-01-01
  • 2019-12-14
  • 1970-01-01
相关资源
最近更新 更多