C++17 std::filesystem::path 中的本机路径分隔符错误?

Native path separator bug in C++17 std::filesystem::path?

我在从 #include <experimental/filesystem> 升级到 #include <filesystem> 时遇到问题。 std::filesystem::path::wstring 方法似乎没有返回与 experimental::filesystem 相同的字符串。我写了以下包含输出结果的小测试程序。

#include <iostream>
#include <filesystem>
#include <experimental/filesystem>

namespace fs = std::filesystem;
namespace ex = std::experimental::filesystem;
using namespace std;

int main()
{
    fs::path p1{ L"C:\temp/foo" };    
    wcout << "std::filesystem Native: " << p1.wstring() << "  Generic: " << p1.generic_wstring() << endl;

    ex::path p2{ L"C:\temp/foo" };
    wcout << "std::experimental::filesystem Native: " << p2.wstring() << "  Generic: " << p2.generic_wstring() << endl;
}

/* Output:
std::filesystem Native: C:\temp/foo  Generic: C:/temp/foo
std::experimental::filesystem Native: C:\temp\foo  Generic: C:/temp/foo
*/

根据https://en.cppreference.com/w/cpp/filesystem/path/string

Return value

The internal pathname in native pathname format, converted to specified string type.

程序 运行 在 Windows 10 上使用 Visual Studio 2017 版本 15.8.0 编译。我希望本机路径名是 C:\temp\foo

问题:这是 std::filesystem::path 中的错误吗?

平台上可以考虑其中任何一个 "native",因此这些选项中的任何一个都同样有效。文件系统 API 不保证 "native" 版本与您提供的字符串相同,无论平台如何。也不能保证 "native" 字符串仅在通用“/”字符与其等效时才使用本机目录分隔符。

粗略地说,当编译器表现出标准(明确或隐含)禁止的行为,或与所述编译器的文档不同的行为时,编译器中就会出现错误。

该标准对本机路径字符串的格式没有任何限制,除了该格式应该被底层操作系统接受(引用如下)。它怎么能施加这样的限制?该语言对主机如何处理路径没有发言权OS,要自信地做到这一点,它必须知道它可能被编译到的每个目标,这显然是不可行的。

[fs.class.path]

5   A pathname is a character string that represents the name of a path. Pathnames are formatted according to the generic pathname format grammar ([fs.path.generic]) or according to an operating system dependent native pathname format accepted by the host operating system.

(强调我的)

The documentation of MSVC 表示正斜杠作为分隔符是完全可以接受的:

Common to both systems is the structure imposed on a pathname once you get past the root name. For the pathname c:/abc/xyz/def.ext:

  • The root name is c:.
  • The root directory is /.
  • The root path is c:/.
  • The relative path is abc/xyz/def.ext.
  • The parent path is c:/abc/xyz.
  • The filename is def.ext.
  • The stem is def.
  • The extension is .ext.

它确实提到了首选分隔符,但这实际上只暗示了 std::make_preferred 的行为,而不是默认路径输出的行为:

A minor difference is the preferred separator, between the sequence of directories in a pathname. Both operating systems let you write a forward slash /, but in some contexts Windows prefers a backslash \.

那么,这是否是错误的问题就很简单了:由于标准没有对行为施加任何限制,并且编译器的文档暗示不需要反斜杠,所以不可能有错误。

左边是这是否是 实施质量 的问题。毕竟,编译器和库的实现者应该知道他们目标的所有怪癖,并相应地实现特性。

在 Windows 中应该使用哪个斜杠('\''/'),或者它是否真的很重要,还有待讨论,因此没有权威的答案。任何主张其中之一的答案都必须非常小心,不要过多地基于意见。此外, path::make_preferred 的存在表明本机路径不一定是首选路径。考虑 零开销 原则:让路径始终是首选路径会给那些在处理路径时不需要那么迂腐的人带来开销。

最后,std::experimental 命名空间就是它在盒子上写的:你不应该期望最终的标准化库的行为与它的实验版本一样,甚至不要期望最终的标准化库会存在根本。在处理实验性的东西时,它就是这样。

不,这不是错误!

string()et al and c_str()/native() return native 路径名格式的内部路径名。

native 是什么意思

MS states, it uses ISO/IEC TS 18822:2015。最终草案在 §4.11 中定义了 本地路径名格式 如下:

The operating system dependent pathname format accepted by the host operating system.

在Windows中,native()return的路径为std::wstring()

如何在 Windows

中强制使用反斜杠作为目录分隔符

标准定义了术语 preferred-separator (see also §8.1 (pathname format grammar)):

An operating system dependent directory separator character.

可以使用 path::make_preferred 将路径转换(就地)为首选分隔符。在 Windows 中,它有 noexcept 运算符。

为什么你不应该担心

MS documentation about paths 说明了 /\

的用法

File I/O functions in the Windows API convert "/" to "\" as part of converting the name to an NT-style name, except when using the "\?\" prefix as detailed in the following sections.

并在 documentation about C++ file navigation, the slash (known as fallback-separator in newer drafts) is even used directly after the root-name:

path pathToDisplay(L"C:/FileSystemTest/SubDir3/SubDirLevel2/File2.txt ");

带有 -std:C++17 的 VS2017 15.8 示例:

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

void output(const std::string& type, fs::path& p)
{
    std::cout
        << type << ":\n"
        << "- native: " << p.string() << "\n"
        << "- generic: " << p.generic_string() << "\n"
        << "- preferred-separator" << p.make_preferred() << "\n";
}

int main()
{
    fs::path local_win_path("c:/dir/file.ext");
    fs::path unc_path("//your-remote/dir/file.ext");

    output("local absolute win path", local_win_path);
    output("unc path", unc_path);

    unc_path = "//your-remote/dir/file.ext"; // Overwrite make_preferred applied above.
    if (fs::is_regular_file(unc_path))
    {
        std::cout << "UNC path containing // was understood by Windows std filesystem";
    }
}

可能的输出(当 unc_path 是现有远程上的现有文件时):

local absolute win path:
- native: c:/dir/file.ext
- generic: c:/dir/file.ext
- preferred-separator"c:\dir\file.ext"
unc path:
- native: //your-remote/dir/file.ext
- generic: //your-remote/dir/file.ext
- preferred-separator"\\your-remote\dir\file.ext"
UNC path containing // was understood by Windows std filesystem

因此,只有在使用强制使用该分隔符进行文件系统交互的库时,才需要对 首选分隔符 进行显式路径转换。