在 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 的宏。
欢迎任何反馈或改进:)
所以我开始使用 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 的宏。
欢迎任何反馈或改进:)