理想情况下,类似 BSL_NATIVE_C_LIB_HEADER() 的宏将继续工作,而在幕后有一种机制可以根据文件名覆盖它们以指向不同的目录。起初这似乎是不可能的,因为宏的参数是一个文件名并且可能包含“。”要么 ”/”。但是有了足够的宏傻瓜,我认为它仍然可以在不更改构建脚本或文件系统的情况下完成。
顺便说一句,我不确定这对 C 预处理器有什么意义,但这里是……
这个想法是构建一系列宏,这些宏将插入到 MSVC 2015 特定块中的 bsl_stdhdrs_incpaths.h 中。有几个这样的宏基本上做同样的事情,所以我已经考虑了解决方案,以便每个用户宏(如 BSL_NATIVE_C_LIB_HEADER)都是根据通用宏 FINDER 实现的。为了便于阅读,实现宏具有短名称,它们应该有某种前缀。
警告:这些示例仅在 GCC 4.8.4 和不稳定、clang-3.5 和 MSVC 2015 上进行了测试。
简单的想法
第一个线索是,即使BSL_NATIVE_C_LIB_HEADER(stddef.h) 的参数包含奇数字符,它也不是单个标记。它是列表 {'stddef', '.', 'h'}。
所以我们可以将 stddef #define 为 ../include/stddef 并且它会起作用,因为 '.'和 'h' 在扩展后被附加!但更好的是,我们可以使用## 将第一个标记粘贴到具有唯一名称的宏中,例如SELECTOR_stddef。然后可以使用基于每个文件名的唯一路径#定义:
#define ANGLES(f) <f>
#define BSL_NATIVE_C_LIB_HEADER(file) ANGLES(SELECTOR_##file)
/* each can now have a different prefix */
#define SELECTOR_stddef ../ucrt/stddef
#define SELECTOR_stdarg ../include/stdarg
/* later on, down in the user code... */
#include BSL_NATIVE_C_LIB_HEADER(stddef.h) /* #include <../ucrt/stddef.h> */
#include BSL_NATIVE_C_LIB_HEADER(stdarg.h) /* #include <../include/stddef.h> */
所以这很好用!唯一的问题是每个被调用的文件都必须有一个选择器。如果没有,它会尝试包含一些看起来像#include <SELECTOR_stdio.h> 的愚蠢文件名,它不会工作得那么好。一些维护程序员可能会通过在 /usr/include 目录中添加一堆符号链接到 SELECTOR_stdio.h 来“修复”它,这对每个人来说都会很糟糕。
如果可以为大多数文件设置默认值,真正会更好。大多数似乎已被改组到 .../ucrt 中,并且只有编译器特定的留在 .../include 中。所以最好覆盖我们想要的。但即使是第一种形式,它也可以正常工作。它的优点是简单。
一堆帮助解决这个问题的问题:
更完整的解决方案
/* presumably within an #if MSVC 2015 conditional in bsl_stdhdrs_incpaths.h */
#define DELIMITER(a) a
/* same as DELIMITER, but named to distinguish the MSVC __VA_ARGS__ bug */
/* workaround is fine to leave in place for standard compilers */
#define MSVCFIXER(a) a
/* add the angle brackets and re-attach the "rest" tokens */
#define FORMATER(x1, x2, pre, rest, ...) <DELIMITER(pre)rest>
/* if __VA_ARGS__ only has one argument, shift so that pre is the default
* otherwise if __VA_ARGS__ has two, pre is the override */
#define SHIFTER(pre, rest, def, ...) MSVCFIXER(FORMATER(__VA_ARGS__, pre, rest, def))
/* expand the commas */
#define EXPANDER(...) MSVCFIXER(SHIFTER(__VA_ARGS__))
/* main implementation - pass both the selector override and default */
#define FINDER(file, defloc) \
EXPANDER(HEAD_LOC_OVERRIDE_##file, DELIMITER(defloc)file,,)
/* now implement the top level macros */
#define BSL_NATIVE_C_LIB_HEADER(file) FINDER(file, HEAD_LOC_DEFAULT_PREFIX)
#define BSL_NATIVE_SYS_TIME_HEADER(file) FINDER(file, HEAD_LOC_DEFAULT_PREFIX)
#define BSL_NATIVE_CISO646_HEADER(file) FINDER(file, /tmp/)
/* maybe define a common default prefix, or hard code it like iso646
* since most files appear to be in ucrt, make this the default
(file.h) will become <../ucrt/file.h> */
#define HEAD_LOC_DEFAULT_PREFIX ../ucrt/
/* override any other files NOTE: the commas
* (stdarg.h) will become <../include/stdarg.h>
* (stdint.h) will become <../include/stdint.h> */
#define HEAD_LOC_OVERRIDE_stdarg ../include/stdarg,
#define HEAD_LOC_OVERRIDE_stdint ../include/stdint,
/* and you can even override the name part too, or remove or add the .h
* (where.h) will become <../somewhere/when> (note: use two commas)
* (sys/*.h) will become <../include/sys/*.h>
* (cstdio) will become <windows.h> */
#define HEAD_LOC_OVERRIDE_where ../somewhere/when,,
#define HEAD_LOC_OVERRIDE_sys ../include/sys,
#define HEAD_LOC_OVERRIDE_cstdio windows.h,
/* later on, down in the user code... */
#include BSL_NATIVE_C_LIB_HEADER(stdarg.h) /* <../include/stdarg.h */
#include BSL_NATIVE_C_LIB_HEADER(stdio.h) /* <../ucrt/stdio.h */
#include BSL_NATIVE_C_LIB_HEADER(cstdio) /* <windows.h> */
#include BSL_NATIVE_C_LIB_HEADER(what.h) /* <../ucrt/what.h> */
#include BSL_NATIVE_C_LIB_HEADER(where.h) /* <../somewhere/when> */
#include BSL_NATIVE_CISO646_HEADER(iso646.h) /* </tmp/iso646.h> */