3

I maintain a project written mostly in C++ that uses the <filesystem> feature of C++17. This code is intended to be run on Linux machines with either clang or gcc as the compiler. Recently, I learned that the current Ubuntu Long-Term Support (LTS) version uses gcc version 7.5 which is much older than the version 10.2 that I am using. The result is that on that version of gcc under Ubuntu LTS, it was still an experimental feature and so was in <experimental/filesystem>. To accomodate this, in each of the relevant source code files I have this:

#if HAS_FILESYSTEM
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#endif

Inspired by this answer, https://stackoverflow.com/a/54290906/3191481 the CMakeFiles.txt file contains this.

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    try_compile(HAS_FILESYSTEM "${CMAKE_BINARY_DIR}/temp" 
        "${CMAKE_SOURCE_DIR}/compilertests/has_filesystem.cpp" 
        CMAKE_FLAGS -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
        LINK_LIBRARIES stdc++fs)
    if (NOT HAS_FILESYSTEM)
        try_compile(HAS_EXPERIMENTAL_FILESYSTEM "${CMAKE_BINARY_DIR}/temp" 
            "${CMAKE_SOURCE_DIR}/compilertests/has_experimental_filesystem.cpp" 
            CMAKE_FLAGS -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
            LINK_LIBRARIES stdc++fs)
    endif()
else()
    try_compile(HAS_FILESYSTEM "${CMAKE_BINARY_DIR}/temp" 
        "${CMAKE_SOURCE_DIR}/compilertests/has_filesystem.cpp")
    if (NOT HAS_FILESYSTEM)
        try_compile(HAS_EXPERIMENTAL_FILESYSTEM "${CMAKE_BINARY_DIR}/temp" 
            "${CMAKE_SOURCE_DIR}/compilertests/has_experimental_filesystem.cpp")
    endif()
endif()

if(HAS_FILESYSTEM)
    message(STATUS "Compiler has filesystem support")
    set(HAS_FILESYSTEM 1)
else()
    if(HAS_EXPERIMENTAL_FILESYSTEM)
        message(STATUS "Compiler has experimental filesystem support")
        set(HAS_FILESYSTEM 0)
    else()
        message(FATAL_ERROR "Compiler is missing filesystem capabilities")
    endif(HAS_EXPERIMENTAL_FILESYSTEM)
endif(HAS_FILESYSTEM)

The two files used by the try_compile lines above are these:

has_filesystem.cpp

#include <filesystem>
int main() {
    std::filesystem::path mypath{"../"};
}

has_experimental_filesystem.cpp

#include <experimental/filesystem>
int main() {
    std::experimental::filesystem::path mypath{"../"};
}

I also use a config file which is populated by CMake and contains this line:

#define HAS_FILESYSTEM @HAS_FILESYSTEM@

My questions

  1. This seems rather ungainly. Is there a better way to accomplish this?
  2. Because I use the HAS_FILESYSTEM value in a config.h.in file, I convert it to a number. Here again, is there a better way to do this?
Edward
  • 6,964
  • 2
  • 29
  • 55
  • Be warned that there are [significant differences](https://stackoverflow.com/q/40899267/8586227) in **using** the two versions as well. Current compilers still support the TS version, so maybe you want to use just it for now if you have to support it anyway? – Davis Herring Nov 21 '20 at 19:27
  • @DavisHerring: It's a good point. My testing has revealed no circumstance in which the two differ in the subset I'm using. – Edward Nov 21 '20 at 20:03

1 Answers1

0

To accomodate this, in each of the relevant source code files I have this:

#if HAS_FILESYSTEM
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#endif

You could put that single snippet of boilerplate in a custom header named something like my_filesystem.hpp and include that where needed instead of the copy-paste of the boilerplate.

Some compilers have a flag that you can use to include a file in every source file, such as GCC's -include flag. If your project only needs to support such compilers, you can then just add this flag to a CMake target using target_compile_options(<target> PRIVATE "SHELL:-include <path>").

You can also inline your has_filesystem.cpp and has_experimental_filesystem.cpp files into your CMake config files by using the check_cxx_source_compiles function, which is just a convenience wrapper around try_compile.

As for your second quesiton, you've only shown one usage of your HAS_FILESYSTEM macro (#if HAS_FILESYSTEM), and I can't see any problem with that.

starball
  • 20,030
  • 7
  • 43
  • 238