用宏构造#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.
我倾向于同意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)
警告:(感谢@jed通过此问题。)如果正在连接的字符串包含在其他地方定义为宏的标识符,则这里将发生意外的宏替换。应该小心避免这种情况,特别是如果Dir
和/或File
不受控制(例如,通过在编译器调用中将其定义为命令行参数)。
您还需要注意,有些实现可能会定义可能在文件路径中以类似令牌的方式显示的单词。例如,GCC可以用unix
和linux
这样的名字定义宏,除非用显式的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)
指出
我尝试了clang, gcc和icc,在godbolt上可用。我不知道它是否可以在Visual Studio中使用。
更准确地说,它半尊重空白:空白被转换为单个空格字符。
我希望包含由宏动态创建的文件路径,用于我的程序中与目标配置相关的部分。
你应该不能这样做(如果你能这样做,你可能不应该这样做)。
你实际上是在试图在源文件中完成编译器的工作,这没有多大意义。如果你想根据你编译的机器改变包含路径,这是一个解决的问题(但不是在头文件中解决的)。
规范的解决方案:
在您的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"
相同的字符串字面值,注意两个片段之间没有+。
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 无法编译 rtmidi 测试 cmidiin.cpp 文件, 非法指令
- 如何将更多文件夹添加到c++include路径
- C++:对不存在的命名空间使用命名空间指令
- 带有特殊路径部分的"std::filesystem::weakly_canonical"失败
- C++A*算法并不总是在路径中具有目标节点
- 从函数角度看ID到文件路径的内部与外部映射
- 函数名是c中该函数的第一条指令的地址吗
- boost xml parsingl将xml的路径作为变量发送
- 对于MacOS上的G++,如何添加默认的include目录/usr/local/include和默认的库搜索路径/usr
- 如何使用cppcheck处理半相对包含路径
- 在C++中设置基于操作系统的文件路径
- 基于编译器选项的编译二进制路径
- 错误:无效的预处理指令 #i 的意思是 #if?
- 组装指令中乘法的下部和上部是什么
- 按边长度递归搜索图中所有可行路径
- 使用变量值作为 PlaySound 中的路径
- 如何转换真实路径 CString c++
- #line 指令的 nul.h 文件名的文件路径不正确
- 用宏构造#include指令的路径