【问题标题】:Construct path for #include directive with macro使用宏构造#include 指令的路径
【发布时间】:2015-11-11 00:34:08
【问题描述】:

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

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

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

【问题讨论】:

    标签: c++ macros include boost-preprocessor


    【解决方案1】:

    我倾向于同意utnapistim's answer 中的评论,即即使可以,也不应该这样做。但是,事实上,您可以使用符合标准的 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 之类的路径触发。

    为避免这些问题,如果您使用此 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. 更准确地说,它半尊重空白:空白被转换为单个空格字符。

    【讨论】:

    • 太棒了。谢谢你花时间在这上面。我暂时使用clang和gcc。 windows_mobile 将是最后一个障碍,并且可能是许多领域中最高的障碍,因为我们使用的是 c++14。
    • 谢谢你。我的情况很明显,由于冲突,我无法使用包含路径值。在我清理干净之前,这将使我的生活更易于管理。
    • 注意在路径中执行扩展的副作用。例如,#include PATH(linux/,userfaultfd.h) 使用 gnu C/C++ 方言(默认使用 gcc 和 clang)失败,因为它们定义了 linux=1,导致尝试包含 "1/userfaultfd.h"。构造类似foo-unix/bar.h 的路径也存在类似问题(因为还定义了unix=1)。在采用这种技术之前,您需要确保您的系统永远不会处理这样的路径。
    • @rici 您可以在逐个编译器的基础上避免使用这些特定的宏,但您可能不希望在分发给用户的标头中出现此类要求。我刚刚遇到了这种用于处理绝对路径的技术,由于父目录的名称而以这种方式失败。
    • @IvanR:不。预处理器中没有从标记中删除引号的机制,并且字符串文字连接发生在预处理之后,因此它不能在#include 指令中使用。所以你需要弄清楚如何在没有引号的情况下做到这一点。对于文件路径,这通常不是问题。一般来说,最好使用构建环境的工具来配置包含路径,这样可以更好地适应任务。
    【解决方案2】:

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

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

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

    规范解决方案:

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

    然后,将包含指令写为:

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

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

    【讨论】:

    • 你能说明为什么这不可能吗?在程序的这一部分中为源文件添加 -I 指令是一个选项,但它会(在我看来)混淆代码,因为不再明显所需的标头位于已在 CMakeLists 中配置的子目录中。 txt文件
    • 应该是不可能的,因为源码不是为构建环境配置include搜索路径的地方(如果一定要写编译器或者编译器规范的话,我不会在里面加宏token替换include 指令参数 - 但我不知道它是否存在)。如果你想让一个被包含的文件属于某个目录很明显,在构建系统中将包含路径一直添加到子目录和源代码中的子目录和文件名。
    • 这样的概念似乎实际上等同于将项目的所有头文件扔到一个目录中,只是命名冲突不会导致文件被覆盖,而是可能导致系统任意选择在同名文件中。能够定义与路径名关联的宏,这样每个#include 语句都会明确地识别它需要的头文件,这看起来更干净。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 不幸的是,字符串连接不会在预处理器中发生,因此在我的特定情况下这不是一个选项。
      • @RichardHodges:嗯,这在技术上取决于预处理器 IIRC。但正如我所说,预处理器指令中的引用文本不是字符串,也不一定表现得像一个字符串。如果它不是字符串,那么不要期望字符串连接。
      猜你喜欢
      • 2018-12-22
      • 1970-01-01
      • 2016-01-26
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多