Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine whether to use <filesystem> or <experimental/filesystem>?

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.

like image 618
BrainStone Avatar asked Nov 18 '18 21:11

BrainStone


People also ask

What is filesystem in c++?

Filesystem library provides facilities to manipulate files and directories, and the paths that identify them. The features of the library include: A modern C++ interface, highly compatible with the C++ standard library.

What is a file system library?

The Filesystem library provides facilities for performing operations on file systems and their components, such as paths, regular files, and directories. The filesystem library was originally developed as boost.


2 Answers

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.

like image 167
BrainStone Avatar answered Oct 02 '22 11:10

BrainStone


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.

like image 38
Chris Mc Avatar answered Oct 02 '22 10:10

Chris Mc