在 C 中扩展宏

Extending a macro in C

所以我开始使用 X-Macros 在 C 中编写模板,并希望使用 _Generic() 来重载它们。问题是这需要扩展宏。我知道我不能有一个自我引用的宏。而且我相信我不能像我希望的那样扩展宏的定义。我知道我可以使用其他技术并完成代码(比如将我的节点从里到外翻转并使用 container_of)但是我最终在实现 typeof() 时会遇到同样的问题所以我认为这将是最好的可能的话现在就解决。

一个玩具示例(我希望可以使用的)是:

#include <stdio.h>

void a(int x){printf("a\n");}
void b(char x){printf("b\n");}

#define mat(x) _Generic((x), int: a(x))
#define temp(x) mat(x)
#define mat(x) _Generic((x), char: b(x), default: temp(x))

int main(void)
    {
    int x;
    mat(x);
    }

有什么方法可以完成这个重新定义或类似的东西吗?如果没有,有没有办法以 POSIX 兼容的方式(M4、sh、其他)?

答案:此代码模式似乎在 C11 中通常很有用,因此请尽可能只使用 C。我真的很讨厌大多数预处理器,所以如果不是 CPP,那绝对是我最后的选择。

事实证明,如果不从头重写宏,就不可能以任何方式更改它。在标准 6.10.3 中,您有:

An identifier currently defined as an object-like macro shall not be redefined by another #define preprocessing directive unless the second definition is an object-like macro definition and the two replacement lists are identical. Likewise, an identifier currently defined as a function-like macro shall not be redefined by another #define preprocessing directive unless the second definition is a function-like macro definition that has the same number and spelling of parameters, and the two replacement lists are identical.

因此,您要么让宏完全根据其定义展开,要么您需要 #undef 它。如果你 #undef 它,要重新定义它,你需要从头开始编写它。届时旧定义将不可用,甚至无法帮助定义新版本。

您不能通过任何类型的中介 "reach" 旧定义,因为预处理器作用于在调用时处于活动状态的定义,而不是在定义所涉及的宏的位置。所以,如果你像你那样定义 temp(x) ,它会在你对 mat 的新定义的上下文中扩展,而不是旧的(更不用说在 mat(x) 和 mat(x) 的两个定义之间需要有一个 undef anti-recursion 规则将在 mat(x) 处停止宏扩展。

简而言之,根据标准,绝对没有办法以基于其原始定义的方式重新定义宏。这在数学上可以从标准中证明。

您有什么理由不修改宏的原始定义来处理您感兴趣的每种类型?或者使用一些修改的命名方案来指示你自己的版本(比如附加 _o 来指示处理更多类型但最终依赖于相应宏的宏)?或者,更好的是,修改原始宏的名称,以便您可以重新定义它们,但仍然可以使用原始宏?

编辑

根据下面的评论,这是一种可能实现 OP 期望的方法。它并不完美,但 C 预处理器确实有局限性。这是一个将 objects 转换为可修改字符串的示例。

将此写在 header 定义宏的第一个位置:

#define get_length(x) get_length_help(x)
#define get_length_help(x) sizeof(#x)

#define to_string(x) _Generic( (x),                                        \
   int: sprintf( (char[get_length(INT_MAX)]){0}, "%d", x ),                \
   char: sprintf( (char[2]){0}, "%c", x ),                                 \
   to_string_1,
   to_string_2,
   // ...
   )

// make sure all extra spots expand to nothing until used
#define to_string_1
#define to_string_2
// ...

// macro to test if an extension slot is used
// requires that extensions do not begin with '('
#define used(...) used_help2( (used_help1 __VA_ARGS__ ()), 0, 1, )
#define used_help1() ),(
#define used_help2(_1,_2,_3,...) _3

写在foo.c

typedef struct {
   int x,
   int y
   } plot_point

// this part is important, all generic selections must be valid expressions
// for the other types as well
#define safe_plot_point(x) _Generic( x, plot_point: x, default: (plot_point){0,0} )

// this could define a comma delimited list of selection statements as well
#define to_string_foo                                                      \
   plot_point: sprintf(                                                    \
      (char[get_length((INT_MAX,INT_MAX))]){0},                            \
      "(%i,%i)",                                                           \
      safe_plot_point(x).x, safe_plot_point(x).y                           \
      )

// this is the only real complication for the user
#if used(to_string_1)
#undef to_string_1
#define to_string_1 to_string_foo
#elif used(to_string_2)
#undef to_string_2
#define to_string_2 to_string_foo
// ...
#else
_Static_assert( 0, "no to_string_#'s left!" );
#endif

因此,您可以获得一定程度的可修改性。请注意,没有宏可以只执行修改,因为宏扩展到另一个预处理指令是未定义的行为,并且如上所述,预处理指令是更改宏行为的唯一方法。宏调用对其他调用视而不见,它们只看到其他定义。

另请注意,您可以定义任意数量的扩展槽。您可以定义比给定文件检查更多的插槽,并且仅在需要时添加额外的行(#if's)。

这是一个古老但有趣的问题,它真正说明了 _Generic 的局限性。基于 Kyle's 答案,我认为如果我们可以将扩展代码从宏文本转换为常规代码,我们可以通过没有任何(可见)插槽或 #undefs 的更清晰的界面实现用户可扩展性。为此,我们需要一些宏技巧和帮助 header.

在下面的示例中,我们希望“foo”是一个 user-extendible function-like 宏,它根据参数类型调用不同的函数(为了演示,该函数不需要参数,只是 return 作为字符串的类型名称)。

foo.h:

#ifndef FOO_H
#define FOO_H
#include <stdio.h>

// The end result of the below macros is that if_foo_type_def( FOO_TYPE_N )( some_code )
// will expand to some_code if FOO_TYPE_N is defined (as an empty macro) and nothing if it
// is not defined
#define concat( a, b ) a ## b
#define if_foo_type_def_( ... ) __VA_ARGS__
#define if_foo_type_def_FOO_TYPE_1( ... )
#define if_foo_type_def_FOO_TYPE_2( ... )
#define if_foo_type_def_FOO_TYPE_3( ... )
#define if_foo_type_def_FOO_TYPE_4( ... )
#define if_foo_type_def_FOO_TYPE_5( ... )
#define if_foo_type_def_FOO_TYPE_6( ... )
#define if_foo_type_def_FOO_TYPE_7( ... )
#define if_foo_type_def_FOO_TYPE_8( ... )
#define if_foo_type_def_FOO_TYPE_9( ... )
#define if_foo_type_def_FOO_TYPE_10( ... )
#define if_foo_type_def_FOO_TYPE_11( ... )
#define if_foo_type_def_FOO_TYPE_12( ... )
#define if_foo_type_def_FOO_TYPE_13( ... )
#define if_foo_type_def_FOO_TYPE_14( ... )
#define if_foo_type_def_FOO_TYPE_15( ... )
#define if_foo_type_def_FOO_TYPE_16( ... )
#define if_foo_type_def_FOO_TYPE_17( ... )
#define if_foo_type_def_FOO_TYPE_18( ... )
#define if_foo_type_def_FOO_TYPE_19( ... )
#define if_foo_type_def_FOO_TYPE_20( ... )
#define if_foo_type_def_FOO_TYPE_21( ... )
#define if_foo_type_def_FOO_TYPE_22( ... )
#define if_foo_type_def_FOO_TYPE_23( ... )
#define if_foo_type_def_FOO_TYPE_24( ... )
#define if_foo_type_def_FOO_TYPE_25( ... )
#define if_foo_type_def_FOO_TYPE_26( ... )
#define if_foo_type_def_FOO_TYPE_27( ... )
#define if_foo_type_def_FOO_TYPE_28( ... )
#define if_foo_type_def_FOO_TYPE_29( ... )
#define if_foo_type_def_FOO_TYPE_30( ... )
#define if_foo_type_def_FOO_TYPE_31( ... )
#define if_foo_type_def_FOO_TYPE_32( ... )
#define if_foo_type_def( type ) concat( if_foo_type_def_, type )

// Extendable macro
#define foo( a )                                                                \
    _Generic( (a),                                                              \
        if_foo_type_def( FOO_TYPE_1 )( foo_type_1_type: foo_type_1_func, )      \
        if_foo_type_def( FOO_TYPE_2 )( foo_type_2_type: foo_type_2_func, )      \
        if_foo_type_def( FOO_TYPE_3 )( foo_type_3_type: foo_type_3_func, )      \
        if_foo_type_def( FOO_TYPE_4 )( foo_type_4_type: foo_type_4_func, )      \
        if_foo_type_def( FOO_TYPE_5 )( foo_type_5_type: foo_type_5_func, )      \
        if_foo_type_def( FOO_TYPE_6 )( foo_type_6_type: foo_type_6_func, )      \
        if_foo_type_def( FOO_TYPE_7 )( foo_type_7_type: foo_type_7_func, )      \
        if_foo_type_def( FOO_TYPE_8 )( foo_type_8_type: foo_type_8_func, )      \
        if_foo_type_def( FOO_TYPE_9 )( foo_type_9_type: foo_type_9_func, )      \
        if_foo_type_def( FOO_TYPE_10 )( foo_type_10_type: foo_type_10_func, )   \
        if_foo_type_def( FOO_TYPE_11 )( foo_type_11_type: foo_type_11_func, )   \
        if_foo_type_def( FOO_TYPE_12 )( foo_type_12_type: foo_type_12_func, )   \
        if_foo_type_def( FOO_TYPE_13 )( foo_type_13_type: foo_type_13_func, )   \
        if_foo_type_def( FOO_TYPE_14 )( foo_type_14_type: foo_type_14_func, )   \
        if_foo_type_def( FOO_TYPE_15 )( foo_type_15_type: foo_type_15_func, )   \
        if_foo_type_def( FOO_TYPE_16 )( foo_type_16_type: foo_type_16_func, )   \
        if_foo_type_def( FOO_TYPE_17 )( foo_type_17_type: foo_type_17_func, )   \
        if_foo_type_def( FOO_TYPE_18 )( foo_type_18_type: foo_type_18_func, )   \
        if_foo_type_def( FOO_TYPE_19 )( foo_type_19_type: foo_type_19_func, )   \
        if_foo_type_def( FOO_TYPE_20 )( foo_type_20_type: foo_type_20_func, )   \
        if_foo_type_def( FOO_TYPE_21 )( foo_type_21_type: foo_type_21_func, )   \
        if_foo_type_def( FOO_TYPE_22 )( foo_type_22_type: foo_type_22_func, )   \
        if_foo_type_def( FOO_TYPE_23 )( foo_type_23_type: foo_type_23_func, )   \
        if_foo_type_def( FOO_TYPE_24 )( foo_type_24_type: foo_type_24_func, )   \
        if_foo_type_def( FOO_TYPE_25 )( foo_type_25_type: foo_type_25_func, )   \
        if_foo_type_def( FOO_TYPE_26 )( foo_type_26_type: foo_type_26_func, )   \
        if_foo_type_def( FOO_TYPE_27 )( foo_type_27_type: foo_type_27_func, )   \
        if_foo_type_def( FOO_TYPE_28 )( foo_type_28_type: foo_type_28_func, )   \
        if_foo_type_def( FOO_TYPE_29 )( foo_type_29_type: foo_type_29_func, )   \
        if_foo_type_def( FOO_TYPE_30 )( foo_type_30_type: foo_type_30_func, )   \
        if_foo_type_def( FOO_TYPE_31 )( foo_type_31_type: foo_type_31_func, )   \
        if_foo_type_def( FOO_TYPE_32 )( foo_type_32_type: foo_type_32_func, )   \
        /* Will cause a compilation error: */                                   \
        default: "ERROR CAUSE: UNDEFINED FOO TYPE"                              \
    )( a )                                                                      \

// Types supported intrinsically
// We could hard code these into the above macro, but I think it's actually less
// complicated and confusing to define them using the same interface provided to
// the user.

#define NEW_FOO_TYPE_TYPE char
#define NEW_FOO_TYPE_FUNC return "char";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned char
#define NEW_FOO_TYPE_FUNC return "unsigned char";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE signed char
#define NEW_FOO_TYPE_FUNC return "signed char";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE int
#define NEW_FOO_TYPE_FUNC return "int";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned int
#define NEW_FOO_TYPE_FUNC return "unsigned int";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE long
#define NEW_FOO_TYPE_FUNC return "long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned long
#define NEW_FOO_TYPE_FUNC return "unsigned long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE long long
#define NEW_FOO_TYPE_FUNC return "long long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned long long
#define NEW_FOO_TYPE_FUNC return "unsigned long long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE float
#define NEW_FOO_TYPE_FUNC return "float";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE double
#define NEW_FOO_TYPE_FUNC return "double";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE long double
#define NEW_FOO_TYPE_FUNC return "long double";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE _Bool
#define NEW_FOO_TYPE_FUNC return "bool";
#include "new_foo_type.h"

#endif

new_foo_type.h:

// Check header is being user properly first
#if !defined( NEW_FOO_TYPE_TYPE ) || !defined( NEW_FOO_TYPE_FUNC )
#error You need to define NEW_FOO_TYPE_TYPE and NEW_FOO_TYPE_FUNC before including a new foo type
#endif

// Convert user-supplied-via-macro type and code into regular code and place it
// in the first empty slot

#define define_foo_type_type_and_func( num )            \
    typedef NEW_FOO_TYPE_TYPE foo_type_##num##_type;    \
                                                        \
    static inline char *foo_type_##num##_func()         \
    {                                                   \
        NEW_FOO_TYPE_FUNC                               \
    }                                                   \

#ifndef FOO_TYPE_1
define_foo_type_type_and_func( 1 )
#define FOO_TYPE_1

#elif !defined( FOO_TYPE_2 )
define_foo_type_type_and_func( 2 )
#define FOO_TYPE_2

#elif !defined( FOO_TYPE_3 )
define_foo_type_type_and_func( 3 )
#define FOO_TYPE_3

#elif !defined( FOO_TYPE_4 )
define_foo_type_type_and_func( 4 )
#define FOO_TYPE_4

#elif !defined( FOO_TYPE_5 )
define_foo_type_type_and_func( 5 )
#define FOO_TYPE_5

#elif !defined( FOO_TYPE_6 )
define_foo_type_type_and_func( 6 )
#define FOO_TYPE_6

#elif !defined( FOO_TYPE_7 )
define_foo_type_type_and_func( 7 )
#define FOO_TYPE_7

#elif !defined( FOO_TYPE_8 )
define_foo_type_type_and_func( 8 )
#define FOO_TYPE_8

#elif !defined( FOO_TYPE_9 )
define_foo_type_type_and_func( 9 )
#define FOO_TYPE_9

#elif !defined( FOO_TYPE_10 )
define_foo_type_type_and_func( 10 )
#define FOO_TYPE_10

#elif !defined( FOO_TYPE_11 )
define_foo_type_type_and_func( 11 )
#define FOO_TYPE_11

#elif !defined( FOO_TYPE_12 )
define_foo_type_type_and_func( 12 )
#define FOO_TYPE_12

#elif !defined( FOO_TYPE_13 )
define_foo_type_type_and_func( 13 )
#define FOO_TYPE_13

#elif !defined( FOO_TYPE_14 )
define_foo_type_type_and_func( 14 )
#define FOO_TYPE_14

#elif !defined( FOO_TYPE_15 )
define_foo_type_type_and_func( 15 )
#define FOO_TYPE_15

#elif !defined( FOO_TYPE_16 )
define_foo_type_type_and_func( 16 )
#define FOO_TYPE_16

#elif !defined( FOO_TYPE_17 )
define_foo_type_type_and_func( 17 )
#define FOO_TYPE_17

#elif !defined( FOO_TYPE_18 )
define_foo_type_type_and_func( 18 )
#define FOO_TYPE_18

#elif !defined( FOO_TYPE_19 )
define_foo_type_type_and_func( 19 )
#define FOO_TYPE_19

#elif !defined( FOO_TYPE_20 )
define_foo_type_type_and_func( 20 )
#define FOO_TYPE_20

#elif !defined( FOO_TYPE_21 )
define_foo_type_type_and_func( 21 )
#define FOO_TYPE_21

#elif !defined( FOO_TYPE_22 )
define_foo_type_type_and_func( 22 )
#define FOO_TYPE_22

#elif !defined( FOO_TYPE_23 )
define_foo_type_type_and_func( 23 )
#define FOO_TYPE_23

#elif !defined( FOO_TYPE_24 )
define_foo_type_type_and_func( 24 )
#define FOO_TYPE_24

#elif !defined( FOO_TYPE_25 )
define_foo_type_type_and_func( 25 )
#define FOO_TYPE_25

#elif !defined( FOO_TYPE_26 )
define_foo_type_type_and_func( 26 )
#define FOO_TYPE_26

#elif !defined( FOO_TYPE_27 )
define_foo_type_type_and_func( 27 )
#define FOO_TYPE_27

#elif !defined( FOO_TYPE_28 )
define_foo_type_type_and_func( 28 )
#define FOO_TYPE_28

#elif !defined( FOO_TYPE_29 )
define_foo_type_type_and_func( 29 )
#define FOO_TYPE_29

#elif !defined( FOO_TYPE_30 )
define_foo_type_type_and_func( 30 )
#define FOO_TYPE_30

#elif !defined( FOO_TYPE_31 )
define_foo_type_type_and_func( 31 )
#define FOO_TYPE_31

#elif !defined( FOO_TYPE_32 )
define_foo_type_type_and_func( 32 )
#define FOO_TYPE_32

#else
#error Sorry, too many foo types!
#endif

// Undef automatically so that the user doesn't have to do it
#undef NEW_FOO_TYPE_TYPE
#undef NEW_FOO_TYPE_FUNC

main.c(用户的文件):

#include "foo.h" // foo macro

// Define two new types

typedef struct
{
    char data;
} my_type;

typedef struct
{
    char data;
} my_other_type;

// Make foo support those types (this is the user's interface)

#define NEW_FOO_TYPE_TYPE my_type
#define NEW_FOO_TYPE_FUNC return "my_type";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE my_other_type
#define NEW_FOO_TYPE_FUNC return "my_other_type";
#include "new_foo_type.h"

// Test

int main()
{
    int a;
    my_type b;
    my_other_type c;
    int *d;
    
    printf( "%s\n", foo( a ) );     // Prints "int"
    printf( "%s\n", foo( b ) );     // Prints "my_type"
    printf( "%s\n", foo( c ) );     // Prints "my_other_type"
    // printf( "%s\n", foo( d ) );  // Causes compiler error because int* has not been added
                                    // and is not supported intrinsically

    return 0;
}

所以要添加对新类型的支持,用户只需要定义NEW_FOO_TYPE_TYPE和NEW_FOO_TYPE_FUNC,然后包含new_foo_type.h。虽然不是很C-ish,但我认为这个界面相当简单直观。

在我的示例中,NEW_FOO_TYPE_FUNC 必须是一个函数 body,但如果您只是从 [=39] 中定义的新函数中调用该函数,它也可以很容易地成为一个函数名称=](首先声明指向 user-supplied 函数的指针,以在其签名不正确时生成警告或错误)。

这种方法的缺点是在幕后,它仍然是“有槽的”,所以你猜你应该合理地支持多少个槽(只要选择一个大的数字)。而且,很多人会认为它是宏地狱。

一个非常好的事情是它可以与任何需要扩展性的宏一起使用,而不仅仅是基于 _Generic 的宏。

欢迎任何反馈或改进:)