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


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

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

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

6704
2017-08-18 07:30


起源



答案:


我倾向于同意评论 utnapistim的回答 尽管你可以,但你不应该这样做。但实际上,您可以使用符合标准的C编译器。 [注1]

有两个问题需要克服。第一个是你不能使用 ## 运算符用于创建不是有效预处理程序标记的东西,并且路径名不符合有效的预处理程序标记,因为它们包括 / 和  字符。 (该  如果令牌以数字开头,那就没问题了 / 永远不会工作。)

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

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

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

注意调用中的空格字符 PATH 将被保留。所以 Path(Dir, File) 将失败。

当然,你不需要复杂的 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. 更准确地说,它半空白:空白被转换为单个空格字符。


10
2017-08-18 16:10



真棒。谢谢你把时间花在这上面。我正在使用clang和gcc作为时间。 windows_mobile将是最后一个障碍,可能是许多领域中最高的障碍,因为我们正在使用c ++ 14。 - Richard Hodges
谢谢你。我的情况显然是由于碰撞我无法使用包含路径值。直到我可以清理它,这将使我的生活更容易管理。 - James


答案:


我倾向于同意评论 utnapistim的回答 尽管你可以,但你不应该这样做。但实际上,您可以使用符合标准的C编译器。 [注1]

有两个问题需要克服。第一个是你不能使用 ## 运算符用于创建不是有效预处理程序标记的东西,并且路径名不符合有效的预处理程序标记,因为它们包括 / 和  字符。 (该  如果令牌以数字开头,那就没问题了 / 永远不会工作。)

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

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

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

注意调用中的空格字符 PATH 将被保留。所以 Path(Dir, File) 将失败。

当然,你不需要复杂的 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. 更准确地说,它半空白:空白被转换为单个空格字符。


10
2017-08-18 16:10



真棒。谢谢你把时间花在这上面。我正在使用clang和gcc作为时间。 windows_mobile将是最后一个障碍,可能是许多领域中最高的障碍,因为我们正在使用c ++ 14。 - Richard Hodges
谢谢你。我的情况显然是由于碰撞我无法使用包含路径值。直到我可以清理它,这将使我的生活更容易管理。 - James


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

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

您正在有效地尝试在源文件中执行编译器的工作,这没有多大意义。如果要根据编译的机器更改包含路径,这是一个已解决的问题(但未在头文件中解决)。

规范解决方案:

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

然后,将include指令写为:

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

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


1
2017-08-18 07:45



你有资格说明为什么这不可能吗?在程序的这一部分中为源文件添加-I指令是一个选项,但它会(在我看来)对代码进行模糊处理,因为不再明显所需的头部位于已在CMakeLists中配置的子目录中。 txt文件 - Richard Hodges
它应该是不可能的,因为源代码不是配置的地方包括构建环境的搜索路径(如果我必须编写编译器或编译器规范,我不会在include指令参数中添加宏令牌替换 - 但我不喜欢不知道它是否在那里。如果要显示包含的文件属于某个目录,请将include路径一直添加到构建系统中的子目录以及源代码中的子目录和文件名。 - utnapistim
这样的概念似乎实际上等同于将项目的所有头文件抛到一个目录中,除了命名冲突不会导致文件被覆盖,而是可能导致系统从类似命名的文件中任意选择。能够定义与路径名相关联的宏,以便每个#include语句能够明确地识别它所需的头,这似乎更清晰。 - supercat


根据你的描述,听起来你发现并非每一个 ""是一个字符串。尤其是, #include "corefoundation/header.h" 看起来像一个普通的字符串但它不是。语法上,引用文本  预处理程序指令适用于编译器,并编译为以null结尾的字符串文字。预处理器指令中的带引号的文本由预处理器以实现定义的方式解释。

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

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


0
2017-08-18 08:29



遗憾的是,字符串连接在预处理器中不会发生,因此在我的特定情况下不是一个选项。 - Richard Hodges
@RichardHodges:嗯,这在技术上取决于预处理器IIRC。但正如我所说,proprocessor指令中的引用文本不是字符串,并且不一定像一个字符串。如果它不是字符串,那么不要指望字符串连接。 - MSalters
好。大家欢呼。 - Richard Hodges


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

0
2017-08-25 07:07