filesystem太难用了!
C++17带来了一个新的库,
filesystem
。
filesystem
的前身是boost里的
boost.filesystem
。后来被引入C++的TS作为可选支持,命名空间在
std::experimental::filesystem
。再后来C++17对其做了一些修改后正式引入标准库,命名空间在
std::filesystem
。
看起来似乎没啥问题,既然是C++17的标准之一,只要我指定了
-std=c++17
就可以直接使用了咯?
NO。
可以看到 gcc直到8.0才算是加入了支持 ,但你在gcc5里就能开C++17了! clang也差不多,7.0才引入 但5.0就能开C++17了!
不过好在我们还有
filesystem TS
,这是gcc6和clang4都已经支持了的。于是问题就变成了“
怎么确定该用
filesystem
还是
filesystem TS
”?
C++20的语言规范里引入了
feature test macro
,即对支持的特性添加对应的预定义宏,这样就能通过判断宏来确定一个特性有没有被支持了。
虽然是C++20的核心语言特性,但从前面可以看出gcc5和clang3.4都已经标注支持这一特性了 。
因此,我们可以用
__cpp_lib_filesystem
和
__cpp_lib_experimental_filesystem
来 分别判断两者有没有被支持。
来个简单的例子吧,假设我们只考虑支持非TS版, 扔进最新的gcc11和clang13里,开C++17 跑一跑:
#if defined(__cpp_lib_filesystem)
# include <filesystem>
#else
# error "no fs"
#endif
int main()
结果是:
prog.cc:4:5: error: "no fs"
# error "no fs"
不是说gcc8就已经支持filesystem了吗?!
显然
__cpp_lib_filesystem
没有被定义,我们可以让gcc/clang输出一下预定义的宏来确认一下:
g++ -xc++ -std=c++17 -dM -E - < /dev/null
然后你会发现根本没有
__cpp_lib_
开头的宏!
其实IDE里查找一下引用也能发现端倪,
这个宏是定义在
<filesystem>
里面的
……
所以只要调换一下顺序就能编译了:
#include <filesystem>
#if defined(__cpp_lib_filesystem)
#else
# error "no fs"
#endif
int main()
大功告成!
大功告成个鬼啊(╯°Д°)╯ ┻━┻
明明是想判断支持
filesystem
再去
include <filesystem>
的,结果要先include才能知道支不支持?
所以到底是先有鸡还是先有蛋?
不过不用急,cppreference上有段描述贴上来看看:
如果包含了头文件 <version> 或下表中的任意对应头文件,则下列各个宏有定义。每个宏都展开成其对应的功能特性被包含于工作草案时的年份与月份的相应整数字面量。
所以这不是bug,是by design!
可能是这种设计比较傻吧,于是后来又添加了
<version>
用来统一提供这些支持性宏的定义。
那好,先
include <version>
再做判断呗:
#include <version>
#if defined(__cpp_lib_filesystem)
# include <filesystem>
#else
# error "no fs"
#endif
int main()
这次保守一点,就扔进 gcc8里测试吧:
prog.cc:1:10: fatal error: version: No such file or directory
#include <version>
^~~~~~~~~
compilation terminated.
这次错在我,前面说了
feature test macro
是C++20的特性,一个支持C++17的编译器找不到
<version>
也是很正常的……
那有没有宏能测试version这个库特性存不存在呢?
你好,没有的。就算有,也会回到鸡蛋问题的死循环……
在这个问题上,msvc就表现得更“友好”一点。
虽然从19.15版(即VS2017 15.8)才开始支持
feature test macro
,但
这些
__cpp_lib_*
是放在
yvals_core.h
里的
,而
这个文件极为基础
,你就算include个
<type_traits>
也能顺便给引用进来,于是也就能正确获得支持特性的宏了。
StackOverflow上有人写了一长串内容来判断到底该用哪个,但可能还是会遇上无法准确识别的情况……
姑且当include问题被解决了吧,但这还不是全部……
拿只支持TS版的gcc6来说吧:
#include <experimental/filesystem>
int main()
std::experimental::filesystem::create_directories("tmp");
return 0;
这样编译是会报错的……
/tmp/ccjFjgso.o: In function `main':
prog.cc:(.text+0x22): undefined reference to `std::experimental::filesystem::v1::create_directories(std::experimental::filesystem::v1::__cxx11::path const&)'
/tmp/ccjFjgso.o: In function `std::experimental::filesystem::v1::__cxx11::path::path<char [4], std::experimental::filesystem::v1::__cxx11::path>(char const (&) [4])':