Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::filesystem::path to convert between separator types

Tags:

c++

c++17

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.

like image 323
platinum95 Avatar asked Feb 04 '19 14:02

platinum95


People also ask

Does make_preferred change the path of a directory separator?

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.

Should C++17's std::filesystem::make_preferred () replace all directory separators?

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.

What is the syntax for the path name of a directory?

The path name has the following syntax: 1 root-name(optional): identifies the root on a filesystem with multiple roots (such as "C:" or "//myserver" ). In case of... 2 root-directory(optional): a directory separator that, if present, marks this path as absolute. If it is missing (and the... 3 Zero or more of the following: More ...

Do alternative directory separators work with/directory separator implementations?

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.


1 Answers

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.

like image 144
eerorika Avatar answered Oct 27 '22 00:10

eerorika