用宏构造#include指令的路径

Construct path for #include directive with macro

本文关键字:指令 路径 #include      更新时间:2023-10-16

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

为例,我想构造一个宏,它可以这样调用:

#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的回答中的评论,即即使您可以这样做也不应该这样做。但是,事实上,您可以使用符合标准的C编译器。[注1]

有两个问题需要克服。首先,您不能使用##操作符创建非有效预处理器令牌的内容,并且路径名不符合有效预处理器令牌的资格,因为它们包括/字符。(。如果令牌以数字开头,是可以的,但是/是行不通的。)

您实际上不需要连接标记以便使用#操作符对它们进行字符串化,因为该操作符将对整个宏参数进行字符串化,并且该参数可能包含多个标记。然而,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这样的路径触发。

为了避免这些问题,我建议您使用以下方法:

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

  • 将序列放在源文件的非常早的位置,理想情况下是在包含任何其他头文件之前,或者至少在标准库之外的任何头文件之前。

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

#define XSTR(x) #x
#define STR(x) XSTR(x)
#define Dir sys
#define File socket.h
#include STR(Dir/File)

指出

  1. 我尝试了clang, gcc和icc,在godbolt上可用。我不知道它是否可以在Visual Studio中使用。

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

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

你应该不能这样做(如果你能这样做,你可能不应该这样做)。

你实际上是在试图在源文件中完成编译器的工作,这没有多大意义。如果你想根据你编译的机器改变包含路径,这是一个解决的问题(但不是在头文件中解决的)。

规范的解决方案:

在您的Makefile或CMakeLists.txt中使用IF,根据Visual Studio中的构建配置使用自定义属性页(或者简单地为您的用户在OS环境中为您的构建设置特定的设置)。

然后,把include指令写成:

#include <filename.h> // no path here

并依赖于环境/构建系统在调用编译器时使该路径可用。

这适用于VS2013。(当然,这可以做得更简单。)

#define myIDENT(x) x
#define myXSTR(x) #x
#define mySTR(x) myXSTR(x)
#define myPATH(x,y) mySTR(myIDENT(x)myIDENT(y))
#define myLIBAEdir D:\Georgy\myprojects\LibraryAE\build\native\include\ //here whitespace!
#define myFile libae.h
#include myPATH(myLIBAEdir,myFile)

从您的描述中,听起来您发现并非每个""都是字符串。特别是,#include "corefoundation/header.h"看起来像一个普通的字符串,但它不是。从语法上讲,在预处理器指令外加引号的文本是用于编译器的,并编译为以空结束的字符串字面值。预处理器指令中的引用文本由预处理器以实现定义的方式解释。

也就是说,示例中的错误是因为Boost粘贴了第二个和第三个令牌:/filename。第一个、第四个和第五个令牌(directory.h)保持不变。这显然不是你想要的。

依赖于自动字符串连接要容易得多。"directory/" "filename"是与"directory/filename"相同的字符串字面值,注意两个片段之间没有+。