【问题标题】:How to determine whether to use <filesystem> or <experimental/filesystem>?如何确定是使用 <filesystem> 还是 <experimental/filesystem>?
【发布时间】:2018-11-18 21:15:29
【问题描述】:

有没有办法确定我是否可以使用标准 &lt;filesystem&gt;(在所有支持 C++17 的现代 C++ 编译器上都可用)或旧编译器使用的 &lt;experimental/filesystem&gt;。 (例如 g++ 6.3,这是 Debian Stretch 上的当前标准版本)

知道使用哪个很重要,因为第一个使用std::filesystem::xxx,后者使用std::experimental::filesystem::xxx

【问题讨论】:

  • 我一直使用命名空间别名,所以将namespace fs = std::filesystem;namespace fs = std::experimental::filesystem; 转换为微不足道然后用别名声明所有内容:fs::path p = ...;
  • "知道使用哪一个很重要,因为 ..." 这比这更重要,因为它们之间存在行为差异。
  • 我羡慕你们对图书馆的支持。
  • @NicolBolas 这就是我在下面的答案中创建 sn-p 的原因,因为它允许根据使用的版本使用预处理器对代码进行调整。

标签: c++ filesystems c++17


【解决方案1】:

我通常会创建一个标题filesystem.hpp,其内容如下:

// 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

如果使用实验性标头,它甚至会为 std::experimental::filesystemstd::filesystem 创建一个别名。
这意味着您可以简单地包含此标头来代替 &lt;filesystem&gt;,使用 std::filesystem::xxx 并享受旧编译器的支持。

关于这个sn-p细节的几点说明:

  • __cpp_lib_filesystem__cpp_lib_experimental_filesystem
    这些是Feature Testing Macros。当相应的标头可用时,它们应该可用。但 VisualStudio 2015(及以下)不支持它们。所以剩下的就是确保我们能够做出准确的评估,而不是依赖不可靠的宏。
  • __has_include()
    虽然大多数编译器确实内置了该宏,但没有保证,因为它不在标准中。我的 sn-p 在使用之前检查它是否存在。如果它不存在,我们假设我们必须使用实验版本来提供最大的兼容性。
  • defined(_MSC_VER) &amp;&amp; !(defined(_HAS_CXX17) &amp;&amp; _HAS_CXX17)
    VisualStudio 的某些版本(即 2015)只有 C++17 的一半实现。 &lt;filesystem&gt; 标头可能存在,但 std::filesystem 不存在。此行检查这种情况并改用实验版本。
  • #error ...
    如果标题检查可用并且我们找不到任何一个标题,我们只会打印一个很好的错误,因为我们无能为力。
  • INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
    您甚至可以获得一个 marco,让您知道正在使用哪个版本,这样您就可以编写自己的预处理器语句来处理版本之间的差异。
  • namespace filesystem = experimental::filesystem;
    这个别名定义只是为了确保您拥有std::filesystem,假设您的编译器允许您这样做(我还没有看到一个不允许这样做的)。
    根据标准,在 std 命名空间中定义任何东西都是未定义的行为。因此,如果您的编译器、concience、同事、代码标准或任何抱怨,只需在上面的块中定义namespace fs = std::experimental::filesystem;,在下面的块中定义namespace fs = std::filesystem;。 (只是为了确定,如果你这样做,请删除 namespace std { 东西)

P.S.:我创建了答案和这个问题,因为我花了很多时间对没有 &lt;filesystem&gt; 标头的旧编译器感到沮丧。经过在具有多个编译器及其版本的多个平台上进行大量研究和测试后,我设法提出了这个通用解决方案。我已经使用 VisualStudio、g++ 和 clang 对其进行了测试(仅适用于实际上至少对 C++17 具有实验性支持的版本)。
如果另一个编译器有问题,请告诉我,我也会让它为它工作。

【讨论】:

  • 我没有投反对票,但这可能是因为从技术上讲,将任何未经授权的内容放入 namespace std未定义的行为。我通常使用不同的命名空间而不是 std
  • 这不是最好的做法,但可以作为一个简单的开箱即用解决方案。因为我个人更喜欢在代码中使用全名。自然可以改成任何东西。虽然这是一个公平的观点,但我会在我的解释中指出这一点。
  • 我想这不太可能引起问题。我什至自己为一些内部程序做过,但后来我将文件系统的别名改为fs::
  • 它不应该在任何体面的编译器上引起问题。但为了安全起见,我添加了一条说明,解释了如果它成为问题的替代方案。
【解决方案2】:

我通常使用feature test macros 来解决这类问题。我目前正面临这个确切的问题,但我使用了 __cpp_lib_filesystemusing 关键字。

// 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

我在 gcc-6 及更高版本以及 clang-6 上使用它,遗憾的是没有旧的工作室副本可供测试,但它适用于 15.7 及更高版本。

【讨论】:

  • 这些是否在标准中定义?
  • 是的!如果您点击链接,它们都附加到提案中。每当委员会批准它时,他们就会分配此信息。 This is true for any C++11 or later feature
  • 我遇到的一个问题是,这些宏仅在 Visual Studio 2017 上加载任何标准头文件后才定义。对于 Visual Studio 2015 及更低版本甚至不存在。我会将它们包含在我的解决方案中,因此具有适当 C++11+ 支持的编译器可以更轻松地确定正确的版本。
  • 您是否必须包含 才能使 _cpp_lib* 宏存在?
  • 使用 C++ 20 提供的标头来检查 C++ 17 中是否存在实验性内容的目的是什么?我可能会扩展它,但是编译器的机会有多大,它能够运行需要 C++ 20 的代码,不支持 filesystem 作为非实验性功能?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多