宏作为预处理指令的参数

3
面对一个问题:是否可以在预处理器中选择#include,我立即想到这是不可能的。
...只有后来才发现它确实是可能的,你只需要注意参数扩展(例如Boost.Preprocessor可以处理)。
虽然如果可能的话,我会避免真正使用这种方式进行包含,但我想知道为什么它会起作用。目前,我无法从C++或C标准中获得有用的理解。
任何预处理指令都允许使用带参数的宏吗?(除了#define/#undef
能否引用并总结一下允许使用这种方法的地方?
以下是一个使用Boost.Preprocessor简单演示的例子,供好奇者参考:
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>

#define INC_LOCAL(a,b)  BOOST_PP_STRINGIZE(BOOST_PP_CAT(BOOST_PP_CAT(a,b),.h))
#define INC_GLOBAL(a,b) BOOST_PP_CAT(BOOST_PP_CAT(<,a),BOOST_PP_CAT(b,>))

#include INC_LOCAL(loc,al)   // #include "local.h"
#include INC_GLOBAL(vect,or) // #include <vector>

更新:参考了C语言标准,澄清问题。
2个回答

7

以下是C++ 2003草案 § 16.2-4 ("源文件包含")的内容:

A preprocessing directive of the form

# include pp-tokens new-line 

(that does not match one of the two previous forms) is permitted. The preprocessing tokens after include in the directive are processed just as in normal text (each identifier currently defined as a macro name is replaced by its replacement list of preprocessing tokens).

C99的第6.10.2-4节也表明了同样的事情。

上述提到的“两种先前的形式”是# include <h-char-sequence># include "q-char-sequence"。这个部分似乎太简单了,无法概括。

对于其他指令,宏展开不会在任何identifier预处理标记上执行(请注意,这种行为并不是由语法定义的,而是由C++ § 16 / C § 6.10定义的):

# if constant-expression new-line [group] 
# ifdef identifier new-line [group] 
# ifndef identifier new-line [group] 
# elif constant-expression new-line [group] 
# else new-line [group] 
# endif new-line 
# include pp-tokens new-line 
# define identifier replacement-list new-line 
# define identifier lparen [identifier-list] ) replacement-list new-line 
# undef identifier new-line 
# line pp-tokens new-line 
# error [pp-tokens] new-line 
# pragma [pp-tokens] new-line 
# new-line 

#line在C++ § 16.4-5 / C § 6.10.4-5中被明确宏展开。C++ § 16.5 / C § 6.10.5中没有提到#error#pragma的展开。C++ § 16.3-7 / C 6.10.3-8指出:

如果一个以#为前缀并紧跟着标识符的预处理记号出现在可以开始预处理指令的地方,那么该标识符不会受到宏替换的影响。

C++ § 16.3.1 / C § 6.10.3.1-1告诉我们,在将宏函数的参数替换到replacement-list时,它们首先会被宏展开。同样,C++ § 16.3.4 / C § 6.10.3.4要求预处理器在替换后展开replacement-list

总之,当使用#if#elif#include#line、宏函数的参数以及替换后的宏函数体时,都需要进行宏展开。我想这就是全部内容了。


好的,pp-tokens 能让我到达 preprocessing-token,但只有 preprocessing-op-or-punc 看起来不错,但只给出了一些操作符。 - Georg Fritzsche
“are processed just as in normal text” 意味着类似于 “identifier(identifier, ...)” 的宏展开吗? - Georg Fritzsche
当然可以,但结果可能会出现未定义的行为。 - outis
我很高兴你能理解任何东西...虽然我有时会使用BOOST_PP宏,但对我来说这都是魔法!我想我将不得不一遍又一遍地阅读这个回复 :/ - Matthieu M.
阅读标准是一种学习的技能。你接触得越多,就会变得越容易。这对于任何讨论都是如此。 - outis
我希望如此,自从我拥有这份副本以来,我一直在努力。这肯定比总是基于“有人说这个有效”的前提行事要好。 - Georg Fritzsche

2

这是C预处理器的一个非常基础的功能 -- 例如,像 #ifdef 这样的指令,除非与可能是宏的参数一起使用(如果您知道该参数不允许是宏,那么 #ifdef 的目的可能是什么?!),否则毫无意义。

我不确定ISO C标准的相关章节会如何帮助您 -- 据我所知,C++标准也不会改变预处理器的操作方式。


更正:我现在不确定参数化宏是否可以在任何预处理指令中使用,我想澄清一下。 - Georg Fritzsche

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接