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

Construct path for #include directive with macro

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

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

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

I would like to have include file paths dynamically created by a macro for a target-configuration-dependent part of my program.

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

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

规范解:

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

然后,将 include 指令写为:

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

并依靠 environment/build 系统在调用编译器时使路径可用。

根据您的描述,您似乎发现并非每个 "" 都是字符串。特别是,#include "corefoundation/header.h" 看起来像一个普通的字符串,但它不是。在语法上,引用文本 outside 预处理器指令用于编译器,并编译为空终止字符串文字。预处理器指令中引用的文本由预处理器以实现定义的方式解释。

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

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

我倾向于同意 utnapistim's answer 中的评论,即即使可以,也不应该这样做。但是,实际上,您可以使用 standard-conformant C 编译器。 [注1]

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

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

下面通过使用 function-like 宏解决了第二个问题,该宏只是 returns 它的参数:

#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 and/or File 不受控制(例如,通过在编译器调用中定义为 command-line 参数)。

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

等路径触发

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

  • 您使用 C(或 C11)standards-conformant 编译器设置,并且

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

此外,如果您可以在没有 space 的情况下编写连接,则不需要 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. 更准确地说,它semi-respects白色space:白色space转换为单个space字符。

这适用于 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)