C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
250: std::filesystem Path Manipulation In Depth
I was digging through some legacy build scripts the other day and found a absolute nightmare of string concatenations used to manage file paths. You know the type: if (path.back() != '/') path += '/'; scattered everywhere. It's fragile, it's ugly, and it's exactly why std::filesystem::path exists. But if you just treat it like a string, you're missing the actual power of the library.
The slash struggle
Let's say I'm building a system to organize game assets. I have a root directory, and I need to put a texture into a specific subfolder. My first instinct—the "old" way—is to just glue strings together.
std::string root = "assets";
std::string folder = "textures";
std::string file = "hero_diffuse.png";
std::string full_path = root + "/" + folder + "/" + file;
// "assets/textures/hero_diffuse.png"
This works on Linux. But what happens if root already ends with a slash? I get assets//textures/.... It's a mess. Let's see how std::filesystem::path handles this. I'll try using the / operator, which is specifically overloaded for path joining.
namespace fs = std::filesystem;
fs::path root = "assets";
fs::path folder = "textures";
fs::path file = "hero_diffuse.png";
fs::path full_path = root / folder / file;
// Result: "assets/textures/hero_diffuse.png" (on Unix)
// Result: "assets\\textures\\hero_diffuse.png" (on Windows)
The magic here is that the / operator doesn't just append a character; it understands the concept of a directory separator. If I change root to "assets/" (with a trailing slash), the operator is smart enough not to add a second one. It just works. It handles the platform-specific separators for me, which is a huge relief when you aren't wanting to write #ifdef _WIN32 every five lines.
Dissecting the filename
Now, let's get into the weeds. Suppose my asset pipeline generates temporary files with suffixes, like hero_diffuse.png.tmp. I need to strip that .tmp, keep the .png, and maybe change the folder.
I'll start by trying to grab the extension. I'm expecting .png, but let's see what actually happens:
fs::path p = "assets/textures/hero_diffuse.png.tmp";
std::cout << p.extension(); // Output: ".tmp"
Ah, right. The extension is defined as the part starting from the last dot. That's not what I wanted. I want the "stem"—the part before the extension. Let's try p.stem().
std::cout << p.stem(); // Output: "hero_diffuse.png"
Okay, so the stem of hero_diffuse.png.tmp is hero_diffuse.png. If I want to get rid of that .tmp and actually "promote" the file to its final version, I can use replace_extension(). But wait, if I call p.replace_extension(".png") on hero_diffuse.png.tmp, I'm just swapping .tmp for .png, leaving me with hero_diffuse.png.png. That's not right.
The trick here is that I need to treat the stem as a path itself if I have double extensions. Or, more simply, I can just pop the extension off entirely:
fs::path p = "assets/textures/hero_diffuse.png.tmp";
p.replace_extension(""); // Now it's "assets/textures/hero_diffuse.png"
Now the file is clean. If I wanted to move this to a "processed" folder, I can use parent_path() to isolate the directory and then build a new path.
Cleaning up the "dot-dot" mess
One thing that always trips people up is when paths get "dirty" with .. (parent directory) or . (current directory) references. This happens a lot when you're resolving relative paths from a config file.
I'll try to create a path that's a bit of a disaster:
fs::path messy = "assets/textures/../sounds/./ambient/wind.wav";
std::cout << messy; // Output: "assets/textures/../sounds/./ambient/wind.wav"
The path object just stores the string as-is. It doesn't automatically resolve the .. because doing so would require checking the actual disk (since a directory could be a symbolic link, and .. from a symlink might lead somewhere unexpected).
But what if I just want the logical simplification? I don't want to touch the disk; I just want the path to look sane. That's where lexically_normal() comes in. Let's try it:
fs::path clean = messy.lexically_normal();
std::cout << clean; // Output: "assets/sounds/ambient/wind.wav"
That's much better. Now, if I actually needed to know where this file lives on the physical drive—resolving all symlinks and absolute paths—I'd use std::filesystem::canonical(). But be careful: canonical() throws an exception if the file doesn't actually exist. lexically_normal() is just string manipulation on steroids; it doesn't care if the folder exists or not.
📋 Practical Task
The Asset Pipeline Path Sanitizer
You are writing a utility for a game engine that cleans up a list of "dirty" asset paths. You are given a std::vector<std::string> of paths that may contain ., .., and temporary extensions (.tmp).
Write a function std::vector<std::string> sanitizeAssets(const std::vector<std::string>& rawPaths) that performs the following transformations on each path:
- Convert the string to a
std::filesystem::path. - If the path ends in
.tmp, remove that extension. - Normalize the path (resolve
.and..logically). - Ensure the path is relative to a folder named
"final_build"(meaning you should prepend"final_build/"to the normalized path). - Return the result as a vector of strings.
Example Input: "assets/textures/../models/player.obj.tmp"
Expected Output: "final_build/assets/models/player.obj"
There are no comments for now.