C++ macro: The ## operator

发布于:2025-02-10 ⋅ 阅读:(24) ⋅ 点赞:(0)

1. The ## operator

The ## (double number sign) operator concatenates two tokens in a macro invocation (text and/or arguments) given in a macro definition.

The last token of the argument for x is concatenated with the first token of the argument for y.
x 的自变量的最后一个标记与 y 的自变量的第一个标记合并。

#define XY(x,y) x##y

Use the ## operator according to the following rules:

  1. The ## operator cannot be the very first or very last item in the replacement list of a macro definition.
    ## 运算符不能是宏定义的替换列表中的第一个或最后一个项。

  2. The last token of the item in front of the ## operator is concatenated with first token of the item following the ## operator.
    ## 运算符前面的项的最后一个标记与 ## 运算符后面的项的第一个标记合并。

  3. Concatenation takes place before any macros in arguments are expanded.
    在展开自变量中的任何宏之前进行合并。

  4. If the result of a concatenation is a valid macro name, it is available for further replacement even if it appears in a context in which it would not normally be available.
    如果合并的结果是有效的宏名称,那么即使它出现在通常不可用的上下文中,也可以进行进一步替换。

  5. If more than one ## operator and/or # operator appears in the replacement list of a macro definition, the order of evaluation of the operators is not defined.

#define ArgArg(x, y) x##y
#define ArgText(x)   x##TEXT
#define TextArg(x)   TEXT##x
#define TextText     TEXT##text
#define CHENG        1
#define YONGQIANG    2

ArgArg(123, 456)
ArgArg(yong, qiang)
ArgText(name)
TextArg(book)
TextText
ArgArg(YONG, QIANG)

Result of macro expansion:

123456
yongqiang
nameTEXT
TEXTbook
TEXTtext
2

https://godbolt.org/

在这里插入图片描述

2. Concatenation (连接)

It is often useful to merge two tokens into one while expanding macros. This is called token pasting or token concatenation. The ## preprocessing operator performs token pasting. When a macro is expanded, the two tokens on either side of each ## operator are combined into a single token, which then replaces the ## and the two original tokens in the macro expansion. Usually both will be identifiers, or one will be an identifier and the other a preprocessing number. When pasted, they make a longer identifier. This isn’t the only valid case. It is also possible to concatenate two numbers (or a number and a name, such as 1.5 and e3) into a number. Also, multi-character operators such as += can be formed by token pasting.
## (token pasting or token concatenation) 允许将不同的标记加入到单个标记中,因此不能是宏定义中的第一个或最后一个标记。

However, two tokens that don’t together form a valid token cannot be pasted together. For example, you cannot concatenate x with + in either order. If you try, the preprocessor issues a warning and emits the two tokens. Whether it puts white space between the tokens is undefined. It is common to find unnecessary uses of ## in complex macros. If you get this warning, it is likely that you can simply remove the ##.
两个标记不能一起形成有效标记,不能粘贴在一起。例如,您不能以任何顺序将 x+ 连接在一起。如果您尝试这样做,预处理器会发出警告并发出两个标记。

Both the tokens combined by ## could come from the macro body, but you could just as well write them as one token in the first place.
## 组合的两个标记可能来自宏主体,但你也可以首先将它们写为一个标记。

Token pasting is most useful when one or both of the tokens comes from a macro argument. If either of the tokens next to an ## is a parameter name, it is replaced by its actual argument before ## executes. As with stringizing, the actual argument is not macro-expanded first. If the argument is empty, that ## has no effect.
当其中一个或两个标记来自宏参数时,标记粘贴最有用。如果 ## 旁边的任一标记是参数名称,则在 ## 执行之前,它会被其实际参数替换。

Keep in mind that the C preprocessor converts comments to whitespace before macros are even considered. Therefore, you cannot create a comment by concatenating / and *.
C 预处理器在处理宏之前会将注释转换为空格。因此,不能通过连接 /* 来创建注释。

You can put as much whitespace between ## and its operands as you like, including comments, and you can put comments in arguments that will be concatenated. However, it is an error if ## appears at either end of a macro body.
您可以在 ## 和其操作数之间放置任意数量的空格,包括注释,并且可以在将要连接的参数中放置注释。但是,如果 ## 出现在宏主体的任一端,则会出现错误。

Consider a C program that interprets named commands.

struct Command {
	char *name;
	void(*function) (void);
};

struct Command commands[] =
{
	{ "quit", quit_command },
	{ "help", help_command },
	...
};

It would be cleaner not to have to give each command name twice, once in the string constant and once in the function name. A macro which takes the name of a command as an argument can make this unnecessary. The string constant can be created with stringizing, and the function name by concatenating the argument with _command.
字符串常量可以通过字符串化创建,函数名称可以通过将参数与 _command 连接来创建。

#define COMMAND(NAME)  { #NAME, NAME ## _command }

struct Command commands[] =
{
	COMMAND(quit),
	COMMAND(help),
	...
};
#include <stdio.h>

#define PASTER( n ) printf_s( "token" #n " = %d\n", token##n )

int main() {
	int token9 = 9;
	PASTER(9);

	return 0;
}

宏展开如下:

#include <stdio.h>

#define PASTER( n ) printf_s( "token" #n " = %d\n", token##n )

int main() {
	int token9 = 9;
	printf_s( "token" "9" " = %d\n", token9 );

	return 0;
}

The C compiler then combines all the adjacent string constants into one long string.
C 编译器将所有相邻的字符串常量组合成一个长字符串。

#include <stdio.h>

#define PASTER( n ) printf_s( "token" #n " = %d\n", token##n )

int main() {
	int token9 = 9;
	printf_s("token9 = %d\n", token9);

	return 0;
}

在这里插入图片描述

3. llama.cpp

https://github.com/ggerganov/llama.cpp

/home/yongqiang/llm_work/llama_cpp_25_01_05/llama.cpp/ggml/include/ggml.h

#define GGML_UNUSED(x) (void)(x)

#define GGML_TENSOR_LOCALS_1(type, prefix, pointer, array) \
    const type prefix##0 = (pointer)->array[0]; \
    GGML_UNUSED(prefix##0);

#define GGML_TENSOR_LOCALS_2(type, prefix, pointer, array) \
    GGML_TENSOR_LOCALS_1    (type, prefix, pointer, array) \
    const type prefix##1 = (pointer)->array[1]; \
    GGML_UNUSED(prefix##1);

#define GGML_TENSOR_LOCALS_3(type, prefix, pointer, array) \
    GGML_TENSOR_LOCALS_2    (type, prefix, pointer, array) \
    const type prefix##2 = (pointer)->array[2]; \
    GGML_UNUSED(prefix##2);

#define GGML_TENSOR_LOCALS(type, prefix, pointer, array) \
    GGML_TENSOR_LOCALS_3  (type, prefix, pointer, array) \
    const type prefix##3 = (pointer)->array[3]; \
    GGML_UNUSED(prefix##3);

GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne);

GGML_TENSOR_LOCALS(size_t, nb1, src1, nb);
const int64_t ne10 = (src1)->ne[0]; (void)(ne10);
const int64_t ne11 = (src1)->ne[1]; (void)(ne11);
const int64_t ne12 = (src1)->ne[2]; (void)(ne12);
const int64_t ne13 = (src1)->ne[3]; (void)(ne13);;

const size_t nb10 = (src1)->nb[0]; (void)(nb10);
const size_t nb11 = (src1)->nb[1]; (void)(nb11);
const size_t nb12 = (src1)->nb[2]; (void)(nb12);
const size_t nb13 = (src1)->nb[3]; (void)(nb13);;

References

[1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/
[2] The ## operator, https://www.ibm.com/docs/en/i/7.5?topic=directives-macro-definition
[3] 3 Macros, https://gcc.gnu.org/onlinedocs/cpp/Macros.html


网站公告

今日签到

点亮在社区的每一天
去签到