Unwanted extra "empty" argument appears when passing in a macro to another macro instead of passing things directly

huangapple go评论77阅读模式
英文:

Unwanted extra "empty" argument appears when passing in a macro to another macro instead of passing things directly

问题

以下是您提供的内容的翻译:

问题(简要)

(以下是完整的最小代码)不知何故,当我将一个简单的参数列表传递给我的宏时,一切都正常。

#pragma message STRINGIZE( ( DUMMY_WRAPPER (initial_argument, arg2, arg3)))

在上述代码编译期间,我看到了来自编译器/预处理器的预期输出:

#pragma message: ( initial_argument { {initial_argument, arg2} , {initial_argument, arg3} }; 2)

然而,当我尝试使用参数定义宏,并将传递给 DUMMY_WRAPPER 时,我会得到一个"额外"的空参数。示例:

#define ARGS initial_argument, arg2, arg3
#pragma message STRINGIZE( ( DUMMY_WRAPPER (ARGS) ))

编译器输出(与正确输出进行比较):

#pragma message: ( initial_argument { {initial_argument, arg2} , {initial_argument, arg3} , {initial_argument, } }; 3 )

如何去掉这个额外的参数?

完整代码(带有说明)

Nota Bene: 我承认这可能是非常糟糕的宏滥用,有更好的方法来使用C++进行元编程,但我只是试图快速让它工作。

我使用GCC/G++进行编译。

这里是可供您测试/在线实验的工作代码和编译器输出:https://godbolt.org/z/wGFbrK

#define COMMA() ,

// 定义用于字符串化宏的例程,类似于BOOST_PP_STRINGIZE
#define STR1(x) #x
#define STRINGIZE(x) STR1(x)

// 定义用于参数计数的例程和子例程
#define NARG(...)                                                        \
  NARG_(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define ARG_N(_1,_2,_3,_4,_5, N, ... ) N
#define NARG_(...) ARG_N(__VA_ARGS__)

// 定义用于"循环"宏扩展的例程,用于处理宏参数列表
#define LOOP_1(M, C, D, x) M(C, x)
#define LOOP_2(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_1(M, C, D, __VA_ARGS__)
#define LOOP_3(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_2(M, C, D, __VA_ARGS__)
#define LOOP_4(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_3(M, C, D, __VA_ARGS__)
#define LOOP_5(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_4(M, C, D, __VA_ARGS__)

// 定义用于连接内容的例程,用于扩展循环例程名称,即LOOP_ + 3 => LOOP_3
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

// **** 顶级 ****
// (使用上述例程的代码来演示问题)

// 列出第一个参数,即循环中的C,以及其他一些参数
#define LIST_FIELD(arg1, a, ... ) {arg1, a}

#define DUMMY_WRAPPER(arg1, ...) DUMMY( arg1, __VA_ARGS__)

#define DUMMY( arg1, ...)                                                   \
	DUMMY_2(arg1, NARG(__VA_ARGS__), __VA_ARGS__)

#define DUMMY_2( arg1, field_count, ...)                                    \
	DUMMY_3(arg1, CAT(LOOP_, field_count), __VA_ARGS__)                     \
	field_count

#define DUMMY_3( arg1, loop, ...)                                           \
	arg1 {                                                                  \
		loop(LIST_FIELD, arg1, COMMA, __VA_ARGS__)   \
	};

#pragma message STRINGIZE( ( DUMMY_WRAPPER (initial_argument, arg2, arg3)))
#define ARGS initial_argument, arg2, arg3
#pragma message STRINGIZE( ( DUMMY_WRAPPER (ARGS) ))

(部分)可能相关的研究(不确定)

https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

不确定这是否可以解释这里的情况,尽管直觉上我认为不是...

英文:

Problem (concise)

(Full minimal code follows) Somehow, when I pass a simple list of arguments to my macro, everything is fine.

#pragma message STRINGIZE( ( DUMMY_WRAPPER (initial_argument, arg2, arg3)))

During compilation of the above, I see the expected output from the compiler/preprocessor:

#pragma message: ( initial_argument { {initial_argument, arg2} , {initial_argument, arg3} }; 2)

However, when I try to define a macro with the arguments and pass that into DUMMY_WRAPPER, I get an "extra" empty argument. Example:

#define ARGS initial_argument, arg2, arg3
#pragma message STRINGIZE( ( DUMMY_WRAPPER (ARGS) ))

Compiler output (compare to correct output):

#pragma message: ( initial_argument { {initial_argument, arg2} , {initial_argument, arg3} , {initial_argument, } }; 3 )

How do I get rid of this extra argument?

Full code (with explanations)

Nota Bene: I acknowledge this is probably really bad macro abuse, and there are much better ways to do meta-programming with C++, but I'm just trying to get this to work quickly.

I used GCC/G++ for compilation.

Here is working code + compiler output for you to test / experiment with online easily: https://godbolt.org/z/wGFbrK

#define COMMA() ,

// routines for stringizing macros, similar to BOOST_PP_STRINGIZE
#define STR1(x) #x
#define STRINGIZE(x) STR1(x)

// routine & subroutines for argument counting
#define NARG(...)                                                        \
  NARG_(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define ARG_N(_1,_2,_3,_4,_5, N, ... ) N
#define NARG_(...) ARG_N(__VA_ARGS__)

// routines for "looped" macro expansion, for processing lists of macro arguments
#define LOOP_1(M, C, D, x) M(C, x)
#define LOOP_2(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_1(M, C, D, __VA_ARGS__)
#define LOOP_3(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_2(M, C, D, __VA_ARGS__)
#define LOOP_4(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_3(M, C, D, __VA_ARGS__)
#define LOOP_5(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_4(M, C, D, __VA_ARGS__)

// routine for concatenating things, used here to expand loop routine names, i.e. LOOP_ + 3 => LOOP_3
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

// **** TOP-LEVEL ****
// (code using above routines to demonstrate the problem)

// lists the first argument, i.e. C in the loop, and some other argument
#define LIST_FIELD(arg1, a, ... ) {arg1, a}

#define DUMMY_WRAPPER(arg1, ...) DUMMY( arg1, __VA_ARGS__)

#define DUMMY( arg1, ...)                                                   \
	DUMMY_2(arg1, NARG(__VA_ARGS__), __VA_ARGS__)

#define DUMMY_2( arg1, field_count, ...)                                    \
	DUMMY_3(arg1, CAT(LOOP_, field_count), __VA_ARGS__)                     \
	field_count

#define DUMMY_3( arg1, loop, ...)                                           \
	arg1 {                                                                  \
		loop(LIST_FIELD, arg1, COMMA, __VA_ARGS__)   \
	};

#pragma message STRINGIZE( ( DUMMY_WRAPPER (initial_argument, arg2, arg3)))
#define ARGS initial_argument, arg2, arg3
#pragma message STRINGIZE( ( DUMMY_WRAPPER (ARGS) ))

https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

Not sure if this is can explain things here, though, intuitively I think not...

答案1

得分: 3

When your compiler sees this: DUMMY_WRAPPER (ARGS),它将使用 ARGS 作为 DUMMY_WRAPPER 的第一个参数,即使 ARGS 的展开包含逗号。

这可以通过从 DUMMY_WRAPPER 中移除第一个参数,只使用 __VA_ARGS__ 来解决:

#define DUMMY_WRAPPER(...) DUMMY(__VA_ARGS__)

或者DUMMY_WRAPPER 包装在另一个宏中:

#define DUMMY_WRAPPER_2(...) DUMMY_WRAPPER(__VA_ARGS__)

实际上,你的代码应该根本无法编译,因为 ... 宏参数必须至少接收一个参数(可以为空)。DUMMY_WRAPPER(x) 是无效的,但 DUMMY_WRAPPER(x,)DUMMY_WRAPPER(x,y) 是可以的。(这在 C++20 中可能已经改变了,我不确定。)

如果你添加 -pedantic-errors,GCC 和 Clang 会拒绝编译这段代码。

我还建议你使用不同的循环方法。你目前使用的方法需要生成 O(n) 的代码来处理 n 个列表元素。

如果你稍微改变宏的语法,可以使用 O(1) 的代码来实现相同的功能:

#include <iostream>

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_end

#define BODY(x) [x]
#define BODY_a(x) BODY(x) BODY_b
#define BODY_b(x) BODY(x) BODY_a
#define BODY_a_end
#define BODY_b_end

#define LOOP(seq) END(BODY_a seq)

int main()
{
    // Prints `[1] [2] [3]`
    std::cout << STR(LOOP( (1)(2)(3) )) << '\n';
}

在这里,LOOP 可以处理任意数量的元素,无需模板宏。但这种方法的灵活性较差,因为你无法从外部向循环体传递任何信息。但对于你的需求应该足够了。

以下是在元素之间插入逗号的版本:

#include <iostream>

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_end

#define BODY(x) [x]
#define BODY_0(x)   BODY(x) BODY_a
#define BODY_a(x) , BODY(x) BODY_b
#define BODY_b(x) , BODY(x) BODY_a
#define BODY_0_end
#define BODY_a_end
#define BODY_b_end

#define LOOP(seq) END(BODY_0 seq)

int main()
{
    // Prints `[1] , [2] , [3]`
    std::cout << STR(LOOP( (1)(2)(3) )) << '\n';
}
英文:

When your compiler sees this: DUMMY_WRAPPER (ARGS)

It will use ARGS as the first parameter of DUMMY_WRAPPER, even if the expansion of ARGS contains commas.

This can be solved by removing the first parameter from DUMMY_WRAPPER, and only using __VA_ARGS__:

#define DUMMY_WRAPPER(...) DUMMY(__VA_ARGS__)

OR by wrapping DUMMY_WRAPPER in an another macro:

#define DUMMY_WRAPPER_2(...) DUMMY_WRAPPER(__VA_ARGS__)

In fact, your code shouldn't compile at all, since a ... macro parameter must receive at least one argument (which can be empty). DUMMY_WRAPPER(x) is invalid, but DUMMY_WRAPPER(x,) and DUMMY_WRAPPER(x,y) are ok. (This might have been changed in C++20, I'm not sure about it.)

GCC and Clang refuse to compile the code if you add -pedantic-errors.


I would also recommend you to use a different approach to looping. The one you're using requires you to generate O(n) code to process n list elements.

It's possible to do the same thing with O(1) code if you change the macro syntax a bit:

#include &lt;iostream&gt;

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_end

#define BODY(x) [x]
#define BODY_a(x) BODY(x) BODY_b
#define BODY_b(x) BODY(x) BODY_a
#define BODY_a_end
#define BODY_b_end

#define LOOP(seq) END(BODY_a seq)

int main()
{
    // Prints `[1] [2] [3]`
    std::cout &lt;&lt; STR(LOOP( (1)(2)(3) )) &lt;&lt; &#39;\n&#39;;
}

Here, LOOP can process any number of elements, without the need for boilerplate macros.

It's less flexible though, as you can't pass any information to the loop body from outside. But it should be enough for your needs.

And here's a version that inserts commas between elements:

#include &lt;iostream&gt;

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_end

#define BODY(x) [x]
#define BODY_0(x)   BODY(x) BODY_a
#define BODY_a(x) , BODY(x) BODY_b
#define BODY_b(x) , BODY(x) BODY_a
#define BODY_0_end
#define BODY_a_end
#define BODY_b_end

#define LOOP(seq) END(BODY_0 seq)

int main()
{
    // Prints `[1] , [2] , [3]`
    std::cout &lt;&lt; STR(LOOP( (1)(2)(3) )) &lt;&lt; &#39;\n&#39;;
}

huangapple
  • 本文由 发表于 2020年1月7日 01:08:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/59616208.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定