How to determine whether to use <filesystem> or <experimental/filesystem>?
I typically use the feature test macros a lot for this type of problem. I am currently faced with this exact problem but I have used __cpp_lib_filesystem
along with the using
keyword.
// since C++ 20
#include <version>
#ifdef __cpp_lib_filesystem
#include <filesystem>
using fs = std::filesystem;
#elif __cpp_lib_experimental_filesystem
#include <experimental/filesystem>
using fs = std::experimental::filesystem;
#else
#error "no filesystem support ='("
#endif
I am using this on gcc-6 and up as well as clang-6, sadly no older copy of studio to test against but it works on 15.7 and up.
I typically create a header filesystem.hpp
with the following content:
// We haven't checked which filesystem to include yet
#ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
// Check for feature test macro for <filesystem>
# if defined(__cpp_lib_filesystem)
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
// Check for feature test macro for <experimental/filesystem>
# elif defined(__cpp_lib_experimental_filesystem)
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
// We can't check if headers exist...
// Let's assume experimental to be safe
# elif !defined(__has_include)
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
// Check if the header "<filesystem>" exists
# elif __has_include(<filesystem>)
// If we're compiling on Visual Studio and are not compiling with C++17, we need to use experimental
# ifdef _MSC_VER
// Check and include header that defines "_HAS_CXX17"
# if __has_include(<yvals_core.h>)
# include <yvals_core.h>
// Check for enabled C++17 support
# if defined(_HAS_CXX17) && _HAS_CXX17
// We're using C++17, so let's use the normal version
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
# endif
# endif
// If the marco isn't defined yet, that means any of the other VS specific checks failed, so we need to use experimental
# ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
# endif
// Not on Visual Studio. Let's use the normal version
# else // #ifdef _MSC_VER
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
# endif
// Check if the header "<filesystem>" exists
# elif __has_include(<experimental/filesystem>)
# define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
// Fail if neither header is available with a nice error message
# else
# error Could not find system header "<filesystem>" or "<experimental/filesystem>"
# endif
// We priously determined that we need the exprimental version
# if INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
// Include it
# include <experimental/filesystem>
// We need the alias from std::experimental::filesystem to std::filesystem
namespace std {
namespace filesystem = experimental::filesystem;
}
// We have a decent compiler and can use the normal version
# else
// Include it
# include <filesystem>
# endif
#endif // #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
It even creates an alias for std::experimental::filesystem
to std::filesystem
if the experimental headers are used.
Which means you can simply include this header in place of <filesystem>
, use std::filesystem::xxx
and enjoy support from older compilers too.
A few notes on the details of this snippet:
__cpp_lib_filesystem
and__cpp_lib_experimental_filesystem
These are Feature Testing Macros. They should be available when the respecitive headers are available. But VisualStudio 2015 (and below) don't support them. So the rest is just to make sure we can make an accurate assesment, instead of relying on unreliable macros.__has_include()
While most compilers do have that macro built in, there is no gurantee, as it is not in the standard. My snippet checks for it's existence before it is used. And in case it doesn't exist, we assume we have to use the experimental version to provide maximum compatibility.defined(_MSC_VER) && !(defined(_HAS_CXX17) && _HAS_CXX17)
Some versions of VisualStudio (namely 2015) have just an half arsed implementation of C++17. And it's possible that the<filesystem>
header exists, butstd::filesystem
doesn't. This line checks for that case and uses the experimental version instead.#error ...
If the header check is available and we can't find either header we just print a nice error, as there's nothing we can do.INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
You even get a marco that let's you know which version is in usage so you could write pre processor statements of your own that deal with the differences between the versions.namespace filesystem = experimental::filesystem;
This alias definition is just for convinice that'll make sure that you'll havestd::filesystem
, assuming your compiler let's you do it (I haven't seen a single one that doesn't allow that).
According to the standard defining anything in thestd
namespace is undefined behavior. So if your compiler, concience, colleguages, code standard or whatever complains, just definenamespace fs = std::experimental::filesystem;
in the upper block andnamespace fs = std::filesystem;
in the lower. (Just to be sure, if you do that, remove thenamespace std {
stuff)
P.S.: I created the answer and this question, because I spent an awful lot of time getting frustrated with older compilers not having the <filesystem>
header. After a fair amount of research and testing on multiple platforms with multiple compilers and versions of them, I managed to come up with this universal solution. I have tested it with VisualStudio, g++ and clang (Only with versions that actually do have at least experimental support for C++17).
Should there be an issue with another compiler, let me know and I'll make it work for it too.