Vix.cpp v2.7.0 is here Read the blog
Skip to content

Path Components

The Path module provides helpers for reading and transforming the visible components of a path string. These APIs are useful when Vix needs to extract a filename, read a parent path, inspect an extension, replace an extension, or split one path into structured parts without accessing the filesystem.

For normal use, include the public header:

cpp
#include <vix/path.hpp>

All public APIs live in the vix::path namespace.

Filename

Use filename() to return the final component of a path.

cpp
auto name = vix::path::filename("/home/user/main.cpp");

if (!name) {
  return name.error();
}

// name.value() == "main.cpp"

The function is purely lexical. It does not check whether the file exists. It only reads the path string, removes trailing separators, then returns the final segment.

cpp
auto name = vix::path::filename("/home/user/project/");

// name.value() == "project"

An empty path returns a structured path error because there is no meaningful component to extract.

Basename

basename() is equivalent to filename() in this module.

cpp
auto base = vix::path::basename("/var/log/system.log");

if (!base) {
  return base.error();
}

// base.value() == "system.log"

Both names are provided because both are common in path-related code. Use filename() when the code is already written in C++ path vocabulary. Use basename() when the surrounding code is closer to CLI or Unix-style naming.

Parent

Use parent() to return the directory portion of a path.

cpp
auto dir = vix::path::parent("/home/user/main.cpp");

if (!dir) {
  return dir.error();
}

// dir.value() == "/home/user"

parent() is lexical. It does not verify that /home/user exists and it does not inspect the filesystem. It only removes the final component from the path string.

When there is no parent component, the function returns an empty string as a successful value.

cpp
auto dir = vix::path::parent("main.cpp");

if (!dir) {
  return dir.error();
}

// dir.value() == ""

That empty string is not an error. It means the path did not contain a directory portion.

Dirname

dirname() is equivalent to parent() in this module.

cpp
auto dir = vix::path::dirname("/var/log/system.log");

if (!dir) {
  return dir.error();
}

// dir.value() == "/var/log"

The two functions exist for the same reason as filename() and basename(): both naming styles are useful depending on the codebase and the kind of path operation being expressed.

Stem

Use stem() to return the filename without its final extension.

cpp
auto st = vix::path::stem("/home/user/main.cpp");

if (!st) {
  return st.error();
}

// st.value() == "main"

The stem is computed from the final filename component, not from the whole path. If the filename has no extension, the filename is returned unchanged.

cpp
auto st = vix::path::stem("/home/user/Makefile");

if (!st) {
  return st.error();
}

// st.value() == "Makefile"

For special names such as . and .., the module preserves the name rather than trying to remove an extension.

Extension

Use extension() to return the final extension of a path, including the leading dot.

cpp
auto ext = vix::path::extension("/home/user/main.cpp");

if (!ext) {
  return ext.error();
}

// ext.value() == ".cpp"

If the filename has no extension, the function succeeds with an empty string.

cpp
auto ext = vix::path::extension("/home/user/Makefile");

if (!ext) {
  return ext.error();
}

// ext.value() == ""

A leading dot alone does not count as a normal extension. This keeps hidden-style filenames such as .env from being treated as files with an env extension.

Checking for an extension

Use has_extension() when code only needs a boolean answer.

cpp
bool has = vix::path::has_extension("/home/user/main.cpp");
// has == true
cpp
bool has = vix::path::has_extension("/home/user/Makefile");
// has == false

Unlike functions such as filename() or extension(), this helper returns a plain boolean. If the path cannot produce a valid filename, the function returns false.

Replacing an extension

Use replace_extension() to replace the final extension of a path.

cpp
auto header = vix::path::replace_extension(
  "/home/user/main.cpp",
  ".hpp"
);

if (!header) {
  return header.error();
}

// header.value() == "/home/user/main.hpp"

The new extension may be passed with or without a leading dot. When it does not start with ., the module adds one.

cpp
auto header = vix::path::replace_extension(
  "/home/user/main.cpp",
  "hpp"
);

// header.value() == "/home/user/main.hpp"

If the path has no extension, the new extension is appended.

cpp
auto file = vix::path::replace_extension(
  "/home/user/Makefile",
  "txt"
);

// file.value() == "/home/user/Makefile.txt"

Passing an empty new extension removes the existing extension when one is present.

cpp
auto file = vix::path::replace_extension(
  "/home/user/main.cpp",
  ""
);

// file.value() == "/home/user/main"

Splitting a path

Use split() when code needs several components from the same path.

cpp
auto parts = vix::path::split("/home/user/main.cpp");

if (!parts) {
  return parts.error();
}

// parts.value().root      == "/"
// parts.value().dirname   == "/home/user"
// parts.value().filename  == "main.cpp"
// parts.value().stem      == "main"
// parts.value().extension == ".cpp"

split() returns a PathParts structure.

cpp
struct PathParts
{
  std::string root;
  std::string dirname;
  std::string filename;
  std::string stem;
  std::string extension;
};

This is useful for diagnostics, generated metadata, project tooling, and code that wants a stable decomposition of the path string. Instead of calling several helpers separately and keeping the results in sync, the caller can request the structured view once.

Root

The root field stores the detected root portion of the path.

cpp
auto parts = vix::path::split("/home/user/main.cpp");

if (!parts) {
  return parts.error();
}

// parts.value().root == "/"

For Windows-style paths, the root can be a drive root or a UNC-style root.

cpp
auto parts = vix::path::split("C:\\Users\\gaspard\\main.cpp");

if (!parts) {
  return parts.error();
}

// parts.value().root == "C:\\"

Root detection is lexical. It reads the shape of the string and does not check the operating system.

Working with components safely

Component functions that return a path string use PathResult. Check the result before reading the value.

cpp
auto name = vix::path::filename("");

if (!name) {
  const auto& err = name.error();
  // err.category().name() == "path"
}

Boolean helpers such as has_extension() return plain values because their failure mode can be represented as false.

cpp
if (vix::path::has_extension("src/main.cpp")) {
  // extension is present
}

This keeps call sites simple while preserving structured errors for operations that need to return transformed path strings or structured path parts.

A practical component workflow

The following example splits a source file path, changes the extension, and prints a generated header path.

cpp
#include <iostream>
#include <vix/path.hpp>

int main()
{
  const std::string source = "/home/gaspard/project/src/main.cpp";

  auto parts = vix::path::split(source);
  if (!parts) {
    std::cerr << parts.error().message() << '\n';
    return 1;
  }

  auto header = vix::path::replace_extension(source, "hpp");
  if (!header) {
    std::cerr << header.error().message() << '\n';
    return 1;
  }

  std::cout << "directory: " << parts.value().dirname << '\n';
  std::cout << "filename: " << parts.value().filename << '\n';
  std::cout << "stem: " << parts.value().stem << '\n';
  std::cout << "extension: " << parts.value().extension << '\n';
  std::cout << "header: " << header.value() << '\n';

  return 0;
}

The example never touches the filesystem. It only reads and transforms the path string. This is the role of the Path module: keep path structure clear before any real filesystem operation is needed.

The next page explains absolute and relative path operations.

Released under the MIT License.