Reputation: 408
I'm working on code that involves loading a file from a path which is constructed as a concatenation of a given "base" path and a secondary relative path, loaded from another file. For example (and where I'm running into an issue), the base path is "assets/models/", and the secondary path is "maps\map.png". Straight concatenation of these two strings gives "assets/models/maps\map.png". When running on POSIX systems this fails to load. Up to now I've been sorting this out by just replacing the backslashes with forward slashes with
std::replace( path.begin(), path.end(), '\\', '/' );
but I'd like to use C++17's std::filesystem::path
to do this instead.
The description of std::filesystem::path::make_preferred()
suggests that it should replace the separators:
"Converts all directory separators in the generic-format view of the path to the preferred directory separator. For example, on Windows, where \ is the preferred separator, the path foo/bar will be converted to foo\bar"
When implemented in the code, however, it doesn't convert anything. I've also verified that std::filesystem::path::preferred_separator is as expected - '/'.
Am I misunderstanding the purpose of make_preferred()
? Or am I just using it wrong?
Here's a cut down version of the code that doesn't work (this isn't the implemented code, but is close enough to it):
const char * loadedPath = "maps\\map.png"
std::string loadedPathStr = std::string( loadedPath );
auto wPath = std::filesystem::path( loadedPathStr );
wPath = wPath.make_preferred();
basePath = std::filesystem::path( "./a/b/" );
auto totalPath = basePath / wPath;
auto wStr = totalPath.generic_string();
std::cout << wStr << std::endl;
This outputs "./a/b/maps\\map.png"
When debugging the implemented code, it looks like wPath
is optimised out; there's no way of inspecting it.
Strangely, when I compile and run this standalone test program, it works as expected:
int main(){
assert( std::filesystem::path::preferred_separator == '/' );
const char * cPath = "maps\\map.png";
std::string path = std::string( cPath );
auto wPath = std::filesystem::path( path );
wPath = wPath.make_preferred();
std::string wStr = wPath.generic_string();
std::cout << wStr << std::endl;
}
This outputs "maps/map.png". I can't read. This also outputs the incorrect value.
Anyone know whats going on here?
EDIT:
Tried compiling with clang (using gcc before), and it works as expected (separator is converted). Ignore this, made a mistake in recompiling.
I'm running this on Linux, and the path exists.
Upvotes: 10
Views: 14046
Reputation: 238351
Am I misunderstanding the purpose of
make_preferred()
?
Not entirely, but subtly yes. A directory separator (in the generic format) is either the preferred separator, or the fallback separator: /
. On systems where the preferred separator is /
(e.g. POSIX), directory separator is only /
. On such systems make_preferred
doesn't modify the path, which explains why it would be optimised out completely.
The simplest way to replace back slashes with forward slashes is std::replace
. However, do note that back slashes are valid characters in file names in POSIX, so such conversion may break the use of file names that use it.
Upvotes: 9
Reputation: 473437
If you want to write cross-platform code with filesystem
, you should try to stick with the generic format. The behavior of all other formats of filesystem strings is implementation-dependent.
An implementation that allows alternative directory separators will treat them as directory separators. But other, perfectly valid, implementations that don't recognize those separators will not recognize them. "/" is always a directory separator; whether "\" is a separator or not depends on the implementation.
make_preferred
converts from the implementation's path format into the generic format. As such, it's behavior is implementation-dependent.
The main reason behind dealing in non-generic formats is when you're getting path strings from the native OS API. Such path strings will likely be in the implementation's format, so path
needs to be able to recognize them and work with them. For string literals built into your program, you should always prefer the generic format (unless your application is OS-specific, or you are selecting different strings based on the OS your code is being used on).
Upvotes: 3