34

Is there a way to determine whether I can use the standard <filesystem> (which is available on all modern C++ compilers that support C++17) or <experimental/filesystem> which is used by older compilers. (For example g++ 6.3, which is the current standard version on Debian Stretch)

Knowing which one to use is imporant because the first uses std::filesystem::xxx and the latter std::experimental::filesystem::xxx.

BrainStone
  • 3,028
  • 6
  • 32
  • 59
  • 4
    I always used a namespace alias so changing over was trivial `namespace fs = std::filesystem;` from `namespace fs = std::experimental::filesystem;` Then declare everything in terms of the alias: `fs::path p = ...;` – Galik Nov 18 '18 at 21:25
  • "*Knowing which one to use is imporant because ...*" It's more important than that, since there are behavioral difference between them. – Nicol Bolas Nov 18 '18 at 21:28
  • i envy your library support. – johnathan Nov 18 '18 at 21:31
  • @NicolBolas that's why I created the snippet in my answer below, as that allows adjustments with preprocessor to the code depending on which version is being used. – BrainStone Nov 18 '18 at 21:37

2 Answers2

36

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, but std::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 have std::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 the std namespace is undefined behavior. So if your compiler, concience, colleguages, code standard or whatever complains, just define namespace fs = std::experimental::filesystem; in the upper block and namespace fs = std::filesystem; in the lower. (Just to be sure, if you do that, remove the namespace 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.

BrainStone
  • 3,028
  • 6
  • 32
  • 59
  • I didn't downvote but it may be because, technically, putting anything unauthorized in `namespace std` is *undefined behavior*. I usually use a different namespace instead of `std`. – Galik Nov 18 '18 at 21:38
  • 1
    It's not the best to do it, but will do for a simple out of the box solution. As I personally prefer to use the full names throught the code. Naturally it can be changed to anything. Though it's a fair point and I'll point it out in my explanations. – BrainStone Nov 18 '18 at 21:40
  • It is unlikely to cause issue I suppose. I even did it myself for some internal programs but later I changed to aliasing the filesystem to `fs::`. – Galik Nov 18 '18 at 21:42
  • 1
    It shouldn't really cause issues on any decent compiler. But just to be safe, I added a note that explains an alternative should it become an issue. – BrainStone Nov 18 '18 at 21:45
8

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.

Julien Marrec
  • 11,605
  • 4
  • 46
  • 63
Chris Mc
  • 445
  • 5
  • 9
  • Are these defined in the standard? – BrainStone Nov 19 '18 at 00:05
  • yep! if you follow the link they are all attached to proposals. Whenever the committee approves it they assign this information. [This is true for any C++11 or later feature](https://en.cppreference.com/w/cpp/feature_test) – Chris Mc Nov 19 '18 at 00:11
  • 1
    One issue I've encountered is that these macros are only defined after loading any standard header on Visual Studio 2017. And don't even exists for Visual Studio 2015 and below. I'll include them in my solution, so compilers with propper C++11+ support can more easily determine the right version. – BrainStone Nov 19 '18 at 17:48
  • 1
    Don't you have to include for the __cpp_lib_* macros to be there? – Flamefire Oct 05 '20 at 15:36
  • 7
    What is the purpose of using a header provided by C++ 20 to check if something is experimental or not in C++ 17? I might be stretching it but what are the chances of a compiler, that is able to run code that requires C++ 20, not supporting `filesystem` as a non-experimental feature? – rbaleksandar Sep 14 '21 at 08:49