6. Conclusion
The four parts come together in a small executable: three library
modules that know nothing about each other and one main.cpp
that imports all three and glues them together with two lambda
callbacks. What follows is a look at that executable in action.
6.1 Sample Output
Matching files are grouped under their containing directory. The
summary line reports how many files were visited (extension-matched)
and how many matched the regex.
$ text_finder /P .. /p .cpp,.ixx /r "std::regex"
../CppTextFinder/Output/src
Output.ixx
../CppTextFinder/EntryPoint/src
main.cpp
42 file(s) visited, 2 file(s) matched
Directories that contain no matching files are hidden by default
(/H true). Set /H false to print every
directory as it is entered — useful for confirming which subtrees
the walker actually visits and which ones the built-in skip list
rejects:
$ text_finder /P . /p .cpp /r "ZZZNOMATCH" /H false
.
./CommandLine
./CommandLine/src
./DirNav
./DirNav/src
./EntryPoint
./EntryPoint/src
./Output
./Output/src
42 file(s) visited, 0 file(s) matched
Verbose mode (/v) echoes every resolved option before
traversal — handy when you want to see exactly which defaults the
parser applied:
$ text_finder /P . /p .cpp /r "int main" /v
Options:
/P .
/r int main
/s true
/H true
/p .cpp
./EntryPoint/src
main.cpp
42 file(s) visited, 1 file(s) matched
6.2 Building & Running
C++23 named modules require CMake 3.28 or later. On Windows use
Visual Studio 2022 (MSVC 19.41+); on Linux/macOS use Clang 17+ or
GCC 14+ with Ninja.
# Windows / Visual Studio 2022
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
# executable lands at build/EntryPoint/Release/text_finder.exe
# Linux / macOS with Ninja
cmake -S . -B build -G Ninja
cmake --build build
# executable lands at build/EntryPoint/text_finder
6.3 Testing
Each library part provides a src/test.cpp file
containing hand-written unit tests. Tests are compiled into a
stand-alone executable by the part's own CMakeLists.txt
and registered with CTest via add_test() — no external
test framework is required.
# Run all tests
ctest --test-dir build --output-on-failure
# Run one part's tests in isolation
ctest --test-dir build -R cmd_line_tests --output-on-failure
ctest --test-dir build -R dir_nav_tests --output-on-failure
ctest --test-dir build -R output_tests --output-on-failure
6.4 Design Takeaways
-
C++23 modules replace headers cleanly. Each
library is a single
.ixx file that both declares
and defines its public API. EntryPoint's
import cmd_line; gives it exactly the exported
symbols and nothing more — no macro pollution, no include-order
fragility.
-
std::function is the callback contract.
Where Rust uses a trait, C++ uses two
std::function members initialised by lambdas
captured on &out. This trades a small runtime
cost per call for freedom in how the application supplies its
hooks.
-
std::regex is slower than the alternative
engines. A backtracking NFA rescans input on each
match attempt; Rust's regex and Python's
re both use DFA-based engines that scan in one
linear pass. On the comparison workload (see
Project Story:
TextFinder — Performance) the C++ variant is roughly the same
speed as the optimized Rust variant, and slower than the Python
variant that's mostly C under the hood.
-
Lazy directory printing avoids a two-pass walk.
Output::on_dir defers the header until the first
match arrives; the walker fires on_dir
unconditionally on every directory and the output layer decides
whether to actually emit anything. This keeps DirNav
simple and pushes the policy decision into Output.
-
Non-throwing filesystem calls. Every
std::filesystem call in DirNav takes an
std::error_code. Permission-denied subdirectories
silently drop out; the walk continues. This matches the
"traverse what you can" behavior of the Rust variant without
wrapping every call in a try.
6.5 References