使用宏构造#include 指令的路径
我希望包含由宏为我的程序的目标配置相关部分动态创建的文件路径.
I would like to have include file paths dynamically created by a macro for a target-configuration-dependent part of my program.
例如,我想构造一个可以像这样调用的宏:
for example, I would like to construct a macro that would be invoked like this:
#include TARGET_PATH_OF(header.h)
这将扩展为这样的内容:
Which will expand to a something like this:
#include "corefoundation/header.h"
当源配置(在这种情况下)为 OSX 时
when the source is configured (in this case) for OSX
到目前为止,所有尝试都失败了.我希望外面有人这样做过?
So far all attempts have failed. I'm hoping someone out there has done this before?
不起作用的例子:
#include <iostream>
#include <boost/preprocessor.hpp>
#define Dir directory/
#define File filename.h
#define MakePath(f) BOOST_PP_STRINGIZE(BOOST_PP_CAT(Dir,f))
#define MyPath MakePath(File)
using namespace std;
int main() {
// this is a test - yes I know I could just concatenate strings here
// but that is not the case for #include
cout << MyPath << endl;
}
错误:
./enableif.cpp:31:13: error: pasting formed '/filename', an invalid preprocessing token
cout << MyPath << endl;
^
./enableif.cpp:26:16: note: expanded from macro 'MyPath'
#define MyPath MakePath(File)
^
./enableif.cpp:25:40: note: expanded from macro 'MakePath'
#define MakePath(f) BOOST_PP_STRINGIZE(BOOST_PP_CAT(Dir,f))
^
/usr/local/include/boost/preprocessor/cat.hpp:22:32: note: expanded from macro 'BOOST_PP_CAT'
# define BOOST_PP_CAT(a, b) BOOST_PP_CAT_I(a, b)
^
/usr/local/include/boost/preprocessor/cat.hpp:29:36: note: expanded from macro 'BOOST_PP_CAT_I'
# define BOOST_PP_CAT_I(a, b) a ## b
^
1 error generated.
推荐答案
我倾向于同意 utnapistim's answer 即使可以,也不应该这样做.但是,事实上,您可以使用符合标准的 C 编译器.[注1]
I tend to agree with the comment in utnapistim's answer that you shouldn't do this even though you can. But, in fact, you can, with standard-conformant C compilers. [Note 1]
有两个问题需要克服.第一个是您不能使用 ##
运算符来创建不是有效预处理器标记的东西,并且路径名不符合有效预处理器标记的条件,因为它们包含 /和 . 个字符.(如果令牌以数字开头,则 . 可以,但 / 永远不会起作用.)
There are two issues to overcome. The first one is that you cannot use the ##
operator to create something which is not a valid preprocessor token, and pathnames do not qualify as valid preprocessor tokens because they include / and . characters. (The . would be ok if the token started with a digit, but the / will never work.)
您实际上不需要连接标记以使用 #
运算符将它们字符串化,因为该运算符将字符串化整个宏参数,并且该参数可能包含多个标记.但是,stringify 尊重空格 [注 2],所以 STRINGIFY(Dir File)
不起作用;这将导致 directory/filename.h"
并且文件名中的多余空间将导致 #include
失败.所以你需要连接 Dir
和 File
没有任何空格.
You don't actually need to concatenate tokens in order to stringify them with the #
operator, since that operator will stringify an entire macro argument, and the argument may consist of multiple tokens. However, stringify respects whitespace [Note 2], so STRINGIFY(Dir File)
won't work; it will result in "directory/ filename.h"
and the extraneous space in the filename will cause the #include
to fail. So you need to concate Dir
and File
without any whitespace.
下面通过使用只返回其参数的类似函数的宏来解决第二个问题:
The following solves the second problem by using a function-like macro which just returns its argument:
#define IDENT(x) x
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define PATH(x,y) STR(IDENT(x)IDENT(y))
#define Dir sys/
#define File socket.h
#include PATH(Dir,File)
警告:(感谢@jed 传递此问题.)如果连接的字符串包含在其他地方定义为宏的标识符,则此处将发生意外的宏替换.应注意避免这种情况,特别是如果 Dir
和/或 File
不受控制(例如,通过在编译器调用中定义为命令行参数).
Warning: (Thanks to @jed for passing on this issue.) If the strings being concatenated contain identifiers which are defined elsewhere as macros, then unexpected macro substitution will occur here. Caution should be taken to avoid this scenario, particularly if Dir
and/or File
are not controlled (for example, by being defined as a command-line parameter in the compiler invocation).
您还需要注意,某些实现可能会定义可能以类似标记的方式出现在文件路径中的单词.例如,GCC 可以定义名称为 unix
和 linux
的宏,除非它是使用显式 C 标准调用的(这不是默认的).这可能由 platform/linux/my-header.h
甚至 linux-specific/my-header.h
等路径触发.
You need to also be aware than some implementations may define words which are likely to show up in a token-like fashion in a file path. For example, GCC may define macros with names like unix
and linux
unless it is invoked with an explicit C standard (which is not the default). That could be triggered by paths like platform/linux/my-header.h
or even linux-specific/my-header.h
.
为避免这些问题,如果您使用此 hack,我建议您:
To avoid these issues, I'd recommend that if you use this hack:
您使用符合 C(或 C11)标准的编译器设置,并且
you use a C (or C11) standards-conformant compiler setting, and
您将序列放在源文件的很早的位置,最好是在包含任何其他头文件之前,或者至少在标准库之外的任何头文件之前.
you place the sequence very early in your source file, ideally before including any other header, or at least any header outside of the standard library.
此外,如果您可以编写不带空格的连接,则不需要 IDENT
宏的复杂性.例如:
Also, you wouldn't need the complication of the IDENT
macro if you could write the concatenation without spaces. For example:
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define Dir sys
#define File socket.h
#include STR(Dir/File)
注意事项
我在 godbolt 上使用了 clang、gcc 和 icc 进行了尝试.我不知道它是否适用于 Visual Studio.
I tried it with clang, gcc and icc, as available on godbolt. I don't know if it works with Visual Studio.
更准确地说,它半尊重空白:空白被转换为单个空格字符.
More accurately, it semi-respects whitespace: whitespace is converted to a single space character.
相关文章