#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
のバグですか?
いいえ、バグではありません!
string()
et al および c_str()
/native()
内部パス名をネイティブパス名の形式。
MSの状態 、それは ISO/IEC TS 18822:2015 を使用します。最終草案では、§4.11でネイティブパス名の形式を次のように定義しています。
オペレーティングシステムに依存するパス名の形式は、ホストオペレーティングシステムで受け入れられます。
Windowsでは、native()
はパスをstd::wstring()
として返します。
標準では、用語 preferred-separator を定義しています( §8.1(パス名の形式の文法) も参照):
オペレーティングシステムに依存するディレクトリ区切り文字。
path::make_preferred
を使用すると、パスを(その場で)優先区切り文字に変換できます。 Windowsでは、noexcept
演算子があります。
パスに関するMSドキュメント は、/
と\
の使用法について述べています
Windows APIのファイルI/O関数は、名前をNTスタイルの名前に変換する一環として「/」を「\」に変換します。ただし、次のセクションで説明する「\?\」プレフィックスを使用する場合を除きます。
C++ファイルナビゲーションに関するドキュメント では、スラッシュ(新しいドラフトでは fallback-separator として知られています)も使用されています 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
したがって、preferred-separatorへの明示的なパス変換は、ファイルシステムの相互作用にそのセパレーターの使用を強制するライブラリーを操作する場合にのみ必要です。
おおまかに言って、コンパイラーのバグは、標準によって(明示的または暗黙的に)禁止されている動作、またはコンパイラーのドキュメントから逸脱した動作を示す場合に発生します。
標準では、ネイティブパス文字列の形式に制限はありませんが、基盤となるオペレーティングシステム(以下の引用)で形式が受け入れられることを除きます。どのようにしてそのような制限を課すことができますか?ホストOSがパスをどのように処理するかについては、この言語には何の意味もありません。自信を持ってそれを行うには、コンパイル先のすべてのターゲットを知る必要がありますが、これは明らかに実現不可能です。
[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
の存在は、ネイティブパスが必ずしも優先パスではないことを示しています。 zero-overheadの原則を検討してください。パスを常に優先パスにすることは、パスを処理するときにそのような面倒なことをする必要がない人々にオーバーヘッドを招くでしょう。
最後に、std::experimental
ネームスペースは、ボックスに記載されているとおりです。最終的な標準化ライブラリが実験版と同じように動作することを期待したり、最終的な標準化ライブラリがまったく存在することを期待したりすることはできません。実験的なものを扱うとき、それはそれがそうである方法です。
それらのいずれか1つがプラットフォームで「ネイティブ」と見なされる可能性があるため、これらのオプションのいずれか1つが等しく有効です。 Filesystem APIは、プラットフォームに関係なく、「ネイティブ」バージョンが指定した文字列と同一であることを保証しません。また、「ネイティブ」文字列がネイティブディレクトリ区切り文字を使用するという保証もありません。