C++ macro: Variadic macros (可变参数宏)

C++ macro: Variadic macros {可变参数宏}

  • [1. Variadic macro extensions (可变参数宏扩展)](#1. Variadic macro extensions (可变参数宏扩展))
  • [2. Variadic macros](#2. Variadic macros)
  • [3. llama.cpp](#3. llama.cpp)
  • References

1. Variadic macro extensions (可变参数宏扩展)

Variadic macro extensions refer to two extensions to C99 and Standard C++ related to macros with variable number of arguments. One extension is a mechanism for renaming the variable argument identifier from __VA_ARGS__ to a user-defined identifier. The other extension provides a way to remove the dangling comma in a variadic macro when no variable arguments are specified.

可变参数宏扩展是指 C99 and Standard C++ 的两个扩展,它们与具有可变数量参数的宏有关。一个扩展是一种将可变参数标识符从 __VA_ARGS__ 重命名为用户定义标识符的机制。另一个扩展提供了一种在未指定可变参数时删除可变参数宏中的悬空逗号的方法。

The following examples demonstrate the use of an identifier in place of __VA_ARGS__. The first definition of the macro debug exemplifies the usual usage of __VA_ARGS__. The second definition shows the use of the identifier args in place of __VA_ARGS__.

#include <stdio.h>

#define DEBUG1(format, ...)  printf(format, ## __VA_ARGS__)
#define DEBUG2(format, args, ...)  printf(format, ## args)

int main() {
	DEBUG1("yong %s\n", "qiang");
	DEBUG2("YONG %s\n", "QIANG");

	DEBUG1("number %d\n", 258);
	DEBUG2("num %d\n", 369);

	return 0;
}

2. Variadic macros

A macro can be declared to accept a variable number of arguments much as a function can. The syntax for defining the macro is similar to that of a function.

#include <stdio.h>

#define EPRINTF(...) printf(__VA_ARGS__)

int main() {
	EPRINTF("yong %s\n", "qiang");  // -> printf("yong %s\n", "qiang");
	EPRINTF("number %d\n", 258);  // -> printf("number %d\n", 258);

	return 0;
}

This kind of macro is called variadic. When the macro is invoked, all the tokens in its argument list after the last named argument (this macro has none), including any commas, become the variable argument. This sequence of tokens replaces the identifier __VA_ARGS__ in the macro body wherever it appears.

当调用宏时,其参数列表中最后一个命名参数 (此宏没有) 之后的所有标记 (包括任何逗号) 都将成为可变参数。此标记序列将替换宏主体中出现的标识符 __VA_ARGS__

The variable argument is completely macro-expanded before it is inserted into the macro expansion, just like an ordinary argument. You may use the # and ## operators to stringize the variable argument or to paste its leading or trailing token with another token.

If your macro is complicated, you may want a more descriptive name for the variable argument than __VA_ARGS__. CPP permits this, as an extension. You may write an argument name immediately before the ...; that name is used for the variable argument. You cannot use __VA_ARGS__ and this extension in the same macro.

如果宏很复杂,你可能需要一个比 __VA_ARGS__ 更具描述性的变量参数名称。CPP 允许这样做,作为扩展。你可以在 ... 之前立即写入参数名称,该名称用于变量参数。

#define EPRINTF(args...) printf(args)

EPRINTF("yong %s\n", "qiang");  // -> printf("yong %s\n", "qiang");
EPRINTF("number %d\n", 258);  // -> printf("number %d\n", 258);

You can have named arguments as well as variable arguments in a variadic macro.

在可变参数宏中,你可以拥有命名参数以及变量参数。

#include <stdio.h>

#define EPRINTF(format, ...) printf(format, __VA_ARGS__)

int main() {
	EPRINTF("yong %s\n", "qiang");  // -> printf("yong %s\n", "qiang");
	EPRINTF("number %d\n", 258);  // -> printf("number %d\n", 258);

	return 0;
}

This formulation looks more descriptive, but historically it was less flexible: you had to supply at least one argument after the format string. In standard C, you could not omit the comma separating the named argument from the variable arguments. (Note that this restriction has been lifted in C++20, and never existed in GNU C)

你必须在格式字符串后提供至少一个参数。在标准 C 中,你不能省略将命名参数与可变参数分隔开的逗号。请注意,此限制已在 C++20 中取消,在 GNU C 中从未存在过。

Furthermore, if you left the variable argument empty, you would have gotten a syntax error, because there would have been an extra comma after the format string.

#define EPRINTF(format, ...) printf(format, __VA_ARGS__)

EPRINTF("yongqiang\n");  // -> printf("yongqiang\n", );

This has been fixed in C++20, and GNU CPP also has a pair of extensions which deal with this problem.

First, in GNU CPP, and in C++ beginning in C++20, you are allowed to leave the variable argument out entirely.

在 GNU CPP 中,以及从 C++20 开始的 C++ 中,你可以完全省略变量参数。

Second, C++20 introduces the __VA_OPT__ function macro. This macro may only appear in the definition of a variadic macro. If the variable argument has any tokens, then a __VA_OPT__ invocation expands to its argument; but if the variable argument does not have any tokens, the __VA_OPT__ expands to nothing.

C++20 引入了 __VA_OPT__ 函数宏。此宏只能出现在可变参数宏的定义中。如果变量参数有任何标记,则 __VA_OPT__ 调用将扩展为其参数;但如果变量参数没有任何标记,则 __VA_OPT__ 将扩展为无。

__VA_OPT__ is also available in GNU C and GNU C++.

#define EPRINTF(format, ...) printf(format __VA_OPT__(,) __VA_ARGS__)

EPRINTF("yongqiang\n");  // -> printf("yongqiang\n" );

EPRINTF("yong %s\n", "qiang");  // -> printf("yong %s\n" , "qiang");
EPRINTF("number %d\n", 258);  // -> printf("number %d\n" , 258);

Historically, GNU CPP has also had another extension to handle the trailing comma: the ## token paste operator has a special meaning when placed between a comma and a variable argument. Despite the introduction of __VA_OPT__, this extension remains supported in GNU CPP, for backward compatibility.

GNU CPP 还提供了另一个扩展来处理尾随逗号:## 连接运算符放在逗号和变量参数之间时具有特殊含义。尽管引入了 __VA_OPT__,但出于向后兼容的目的,GNU CPP 仍支持此扩展。

The variable argument is left out when the EPRINTF macro is used, then the comma before the ## will be deleted. This does not happen if you pass an empty argument, nor does it happen if the token preceding '##' is anything other than a comma.

使用 EPRINTF 宏时,变量参数被省略,那么 ## 之前的逗号将被删除。如果传递的是空参数,则不会发生这种情况,如果 ## 之前的标记不是逗号,也不会发生这种情况。

#include <stdio.h>

#define EPRINTF(format, ...) printf(format, ##__VA_ARGS__)

int main() {
	EPRINTF("yongqiang\n");  // -> printf("yongqiang\n");

	EPRINTF("yong %s\n", "qiang");  // -> printf("yong %s\n", "qiang");
	EPRINTF("number %d\n", 258);  // -> printf("number %d\n", 258);

	return 0;
}

The above explanation is ambiguous about the case where the only macro parameter is a variable arguments parameter, as it is meaningless to try to distinguish whether no argument at all is an empty argument or a missing argument. CPP retains the comma when conforming to a specific C standard. Otherwise the comma is dropped as an extension to the standard.

对于唯一的宏参数是可变参数参数的情况,上述解释含糊不清,因为试图区分没有参数是空参数还是缺失参数是毫无意义的。当符合特定的 C 标准时,CPP 会保留逗号。否则,逗号将被删除,作为对标准的扩展。

The C standard mandates that the only place the identifier __VA_ARGS__ can appear is in the replacement list of a variadic macro. It may not be used as a macro name, macro argument name, or within a different type of macro. It may also be forbidden in open text; the standard is ambiguous. We recommend you avoid using it except for its defined purpose.

C 标准规定标识符 __VA_ARGS__ 只能出现在可变宏的替换列表中。它不能用作宏名、宏参数名,也不能用于其他类型的宏中。在公开文本中也可能禁止使用它;该标准含糊不清。我们建议您避免使用它,除非它有明确的用途。

Likewise, C++ forbids __VA_OPT__ anywhere outside the replacement list of a variadic macro.

同样,C++ 禁止在可变宏的替换列表之外的任何地方使用 __VA_OPT__

Variadic macros became a standard part of the C language with C99. GNU CPP previously supported them with a named variable argument (args..., not ... and __VA_ARGS__), which is still supported for backward compatibility.

可变参数宏在 C99 中成为 C 语言的标准组成部分。GNU CPP 以前使用命名变量参数 (args..., not ... and __VA_ARGS__) 支持它们,现在仍支持该参数以实现向后兼容。

3. llama.cpp

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

#include <stdio.h>

#define GGML_DEBUG 99

#if (GGML_DEBUG >= 1)
#define GGML_PRINT_DEBUG(...) printf(__VA_ARGS__)
#else
#define GGML_PRINT_DEBUG(...)
#endif

#if (GGML_DEBUG >= 5)
#define GGML_PRINT_DEBUG_5(...) printf(__VA_ARGS__)
#else
#define GGML_PRINT_DEBUG_5(...)
#endif

#if (GGML_DEBUG >= 10)
#define GGML_PRINT_DEBUG_10(...) printf(__VA_ARGS__)
#else
#define GGML_PRINT_DEBUG_10(...)
#endif

#define GGML_PRINT(...) printf(__VA_ARGS__)

int main() {
	GGML_PRINT("cheng\n");  // -> printf("cheng\n");

	GGML_PRINT_DEBUG("yongqiang\n");  // -> printf("yongqiang\n");

	GGML_PRINT_DEBUG_5("yong %s\n", "qiang");  // -> printf("yong %s\n", "qiang");

	GGML_PRINT_DEBUG_10("number %d\n", 258);  // -> printf("number %d\n", 258);

	return 0;
}

References

[1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

[2] Variadic macro extensions, https://www.ibm.com/docs/en/i/7.5?topic=macros-variadic-macro-extensions

[3] 3 Macros, https://gcc.gnu.org/onlinedocs/cpp/Macros.html

相关推荐
Yongqiang Cheng3 天前
C++ macro: The ## operator
c++ macro·## operator