Исходная ошибка разделителя пути в С++ 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:

Возвращаемое значение

Внутреннее имя пути в формате родного пути, преобразованное в указанный тип строки.

Программа работала в Windows 10 и была скомпилирована с Visual Studio 2017 версии 15.8.0. Я бы ожидал, что родной путь будет C:\temp\foo.

Вопрос: Это ошибка в std::filesystem::path?

Ответ 1

Нет, это не ошибка!

string() et al и c_str()/native() возвращают внутренний путь в собственном формате пути.

Какой родной означает

MS, он использует ISO/IEC TS 18822: 2015. Окончательный проект определяет формат родного пути в §4.11 следующим образом:

Формат, зависящий от операционной системы, принятый операционной системой хоста.

В Windows native() возвращает путь как std::wstring().

Как принудительно использовать обратную косую черту как разделитель каталогов в Windows

Стандарт определяет термин " предпочтительный разделитель" (см. Также §8.1 (грамматика формата имени пути)):

Идентификатор сепаратора каталога, зависящий от операционной системы.

Путь может быть преобразован (на место) в предпочтительный разделитель с помощью path::make_preferred. В Windows он имеет оператор noexcept.

Почему вы не должны волноваться

Документация MS о путях указывает на использование / vs \

Функции ввода-вывода файлов в Windows API конвертируют "/" в "\" как часть преобразования имени в имя NT-стиля, за исключением случаев использования префикса "\?", Как описано в следующих разделах.

и в документации о навигации по C++, косая черта (известная как fallback-separator в новых черновиках) даже используется непосредственно после корневого имени:

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

Пример для VS2017 15.8 с -std:C++17:

#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

Таким образом, явные преобразования пути в предпочтительный разделитель должны быть необходимы только при работе с библиотеками, которые обеспечивают использование этого разделителя для взаимодействия с файловой системой.

Ответ 2

Грубо говоря, ошибка в компиляторе происходит, когда она демонстрирует поведение, которое запрещено стандартом (явно или неявно) или поведение, которое расходится с документацией указанного компилятора.

Стандарт не налагает ограничений на формат собственных строк пути, за исключением того, что формат должен приниматься базовой операционной системой (см. Ниже). Как это может налагать такие ограничения? Язык не имеет права говорить о том, как обрабатываются пути операционной системой хоста, и чтобы сделать это уверенно, ему нужно было бы знать каждую цель, к которой она может быть скомпилирована, что явно не представляется возможным.

[fs.class.path]

5 Путь - это строка символов, представляющая имя пути. Имена путей форматируются в соответствии с общей грамматикой формата имени пути ([fs.path.generic]) или в соответствии с зависимым от операционной системы форматом имени пути, принятым операционной системой хоста.

(Акцент мой)

Документация MSVC подразумевает, что косая черта совершенно приемлема как разделитель:

Общей для обеих систем является структура, наложенная на путь после того, как вы пройдете корневое имя. Для имени пути c: /abc/xyz/def.ext:

  • Корневое имя - c:
  • Корневой каталог - /.
  • Корневой путь - c:/.
  • Относительный путь - abc/xyz/def.ext.
  • Родительский путь - c: /abc/xyz.
  • Имя файла - def.ext.
  • Стебель - def.
  • Расширение - .ext.

Он упоминает предпочтительный разделитель, но на самом деле это подразумевает поведение std::make_preferred, а не выход по умолчанию:

Небольшое различие является предпочтительным разделителем между последовательностью каталогов в пути. Обе операционные системы позволяют писать косую черту /, но в некоторых контекстах Windows предпочитает обратную косую черту \.

Поэтому вопрос о том, является ли это ошибкой, легко: поскольку стандарт не налагает никаких ограничений на поведение, а документация компилятора не подразумевает обязательной необходимости обратной косой черты, ошибки не может быть.

Слева - вопрос о том, является ли это проблемой качества реализации. В конце концов, разработчики компилятора и библиотеки должны знать все причуды относительно своей цели и соответственно реализовать функции.

Это для обсуждения, в котором слэш ('\' или '/') вы должны использовать в Windows, или это действительно имеет значение вообще, поэтому не может быть авторитетного ответа. Любой ответ, который защищает тот или иной, должен быть очень осторожным, чтобы не быть слишком основанным на мнениях. Кроме того, простое существование path::make_preferred указывает, что собственный путь не обязательно является предпочтительным. Рассмотрим принцип с нулевыми накладными расходами: для того, чтобы путь всегда был предпочтительным, возникли бы накладные расходы для людей, которым не нужно быть таким педантичным при обращении с путями.

Наконец, пространство имен std::experimental - это то, что он говорит в поле: вы не должны ожидать, что окончательная стандартизованная библиотека будет вести себя так же, как и ее экспериментальная версия, или даже ожидать, что окончательная стандартизованная библиотека вообще будет существовать. Это так, как есть, когда речь идет о экспериментальных материалах.

Ответ 3

Любой из них может считаться "родным" на платформе, поэтому один из этих вариантов одинаково важен. API файловой системы не гарантирует, что "родная" версия будет идентична строке, которую вы ей дали, независимо от платформы. Также нет гарантии, что "родная" строка будет использовать только собственный разделитель каталогов, если общий символ "/" эквивалентен ему.