c++ - 使用宏构造#include 指令的路径

标签 c++ macros include boost-preprocessor

我希望包含由宏为我的程序的目标配置相关部分动态创建的文件路径。

例如,我想构造一个可以像这样调用的宏:

#include TARGET_PATH_OF(header.h)

这将扩展为这样的内容:

#include "corefoundation/header.h"

当源配置(在这种情况下)为 OSX 时

到目前为止,所有尝试都失败了。我希望外面有人这样做过?

不起作用的例子:

#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]

有两个问题需要克服。第一个是您不能使用 ## 运算符来创建不是有效预处理器标记的东西,并且路径名不符合有效预处理器标记的条件,因为它们包含 /. 个字符。 (如果 token 以数字开头,则 . 可以,但 / 永远不会起作用。)

您实际上不需要连接标记以使用 # 运算符将它们字符串化,因为该运算符将字符串化整个宏参数,并且该参数可能包含多个标记。但是,stringify 尊重空格 [注 2],所以 STRINGIFY(Dir File) 不起作用;它将导致 "directory/filename.h" 并且文件名中的多余空间将导致 #include 失败。所以你需要连接 DirFile 没有任何空格。

下面通过使用只返回其参数的类似函数的宏来解决第二个问题:

#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 不受控制(例如,通过在编译器调用中定义为命令行参数)。

您还需要注意,某些实现可能会定义可能以类似标记的方式出现在文件路径中的单词。例如,GCC 可以定义名称为 unixlinux 的宏,除非它是使用显式 C 标准调用的(这不是默认的)。这可能由 platform/linux/my-header.h 甚至 linux-specific/my-header.h 等路径触发。

为避免这些问题,如果您使用此 hack,我建议您:

  • 您使用符合 C(或 C11)标准的编译器设置,并且

  • 您将序列放在源文件的很早的位置,最好是在包含任何其他头文件之前,或者至少在标准库之外的任何头文件之前。

此外,如果您可以编写不带空格的连接,则不需要 IDENT 宏的复杂性。例如:

#define XSTR(x) #x
#define STR(x) XSTR(x)

#define Dir sys
#define File socket.h

#include STR(Dir/File)

注意事项

  1. 我在 godbolt 上使用了 clang、gcc 和 icc 进行了尝试.我不知道它是否适用于 Visual Studio。

  2. 更准确地说,它半尊重空白:空白被转换为单个空格字符。

关于c++ - 使用宏构造#include 指令的路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32066204/

相关文章:

c++ - 我的内存没有被释放

c - 用于在 C 中将符号与变量值连接的预处理器指令

recursion - Lisp递归宏问题

c++ - 我可以依赖嵌套的#includes 吗?

scope - 如何在 jmeter 中重用测试片段?

c++ - 配置itkvtkglue时CMake报错

c++ - 模板按值返回,指针在cpp/h

c++ - 仿函数与模板参数

c++ - 在原始字符串中扩展宏

php - 评估 php 生成的 javascript "inline"?