A lightweight, header-only command-line argument parser for C++
- Header-only library - Just include
cpp_args.hppand you're ready to go - Flexible naming - Supports both
--log_lvland--log-lvlformats (underscores automatically converted to dashes) - Short & long options - Support for both
-pand--portstyle arguments - Flag support - Boolean flags that don't require values (e.g.,
--help,-v) - Value validation - Define allowed values for specific arguments
- Joined options - Support attached values like
-lcudaor--library=cuda - Positional inputs - Non-option arguments are preserved as positional inputs
- Unknown option handling - Configurable handling of unknown options with retrieval API
- X-Macros - Clean, maintainable option definitions using X-Macro pattern
- Unified syntax - Define options and flags together with a single macro
#include "cpp_args.hpp"
#include <iostream>
// Define all arguments in one place - mix options and flags freely
#define MY_APP_ARGS(F) \
F(host, H, "Server hostname", OPTION, {}) \
F(port, p, "Server port", OPTION, {"8080", "9090"}) \
F(library, L, "Link library", JOINED, {"cuda", "stdc++"}) \
F(verbose, v, "Enable verbose mode", FLAG, {}) \
F(help, h, "Print help message", FLAG, {})
DEFINE_ARGS(MyApp, MyAppTable, MY_APP_ARGS)
int main(int argc, char* argv[]) {
ArgumentParser parser(MyAppTable);
if (!parser.Parse(argc, argv)) {
std::cerr << "Failed to parse arguments" << std::endl;
parser.PrintHelp();
return 1;
}
if (parser.HasArg(OPT_help)) {
parser.PrintHelp();
return 0;
}
if (parser.HasArg(OPT_verbose)) {
std::cout << "Verbose mode enabled" << std::endl;
}
if (parser.HasArg(OPT_host)) {
std::cout << "Host: " << parser.GetArgValue(OPT_host) << std::endl;
}
if (parser.HasArg(OPT_port)) {
std::cout << "Port: " << parser.GetArgValue(OPT_port) << std::endl;
}
if (parser.HasArg(OPT_library)) {
std::cout << "Library: " << parser.GetArgValue(OPT_library) << std::endl;
}
// Access positional inputs
const auto& inputs = parser.GetInputs();
for (const auto& input : inputs) {
std::cout << "Input file: " << input << std::endl;
}
return 0;
}# Regular options with values (space-separated)
./my_app --host localhost --port 8080
./my_app -H localhost -p 9090
# Joined options (value attached directly)
./my_app -Lcuda # Short option with joined value
./my_app --library=cuda # Long option with = separator
./my_app -Lstdc++ -I/usr/include # Multiple joined options
# Flags (no value needed)
./my_app --verbose
./my_app -v
./my_app --help
# Positional inputs (non-option arguments)
./my_app -o out.txt input1.txt input2.txt
./my_app file1.txt -v file2.txt file3.txt
# Mixed usage
./my_app -v --host localhost -Lcuda --port 8080 input.txt
./my_app --verbose -H 127.0.0.1 -Lpthread -p 9090 *.txt
# Underscore/dash flexibility (both work!)
./my_app --log-lvl debug # with dash
./my_app --log_lvl debug # with underscore
Options (require separate value):
F(name, short_name, "help text", OPTION, {allowed_values})Flags (no value required):
F(name, short_name, "help text", FLAG, {})Joined Options (value attached directly):
F(name, short_name, "help text", JOINED, {allowed_values})Supports:
- Short format:
-Xvalue(e.g.,-lcuda,-I/usr/include) - Long format:
--name=value(e.g.,--library=cuda)
Parameters:
name: Option identifier (use underscores, e.g.,log_lvl)short_name: Single character short form (e.g.,pfor-p)help_text: Description shown in help messageOPTION,FLAG, orJOINED: Kind identifierallowed_values: For OPTION/JOINED - list of valid values like{"json", "xml"}, use{}for any value
#define MY_ARGS(F) \
F(option1, o, "Option 1", OPTION, {}) \
F(option2, O, "Option 2", OPTION, {"a", "b", "c"}) \
F(joined1, j, "Joined opt", JOINED, {"x", "y"}) \
F(flag1, f, "Flag 1", FLAG, {}) \
F(flag2, F, "Flag 2", FLAG, {})
DEFINE_ARGS(EnumName, TableName, MY_ARGS)
This generates:
- Enum:
OPT_option1,OPT_option2,OPT_joined1,OPT_flag1,OPT_flag2 - OptionTable:
TableNamefor the parser
| Method | Description |
|---|---|
Parse(argc, argv) |
Parse command-line arguments, returns true on success |
HasArg(id) |
Check if an argument was provided (works for both options and flags) |
GetArgValue(id) |
Get the value of an option (empty string for flags or missing args) |
PrintHelp() |
Display usage information with all options and flags |
GetInputs() |
Get positional inputs (non-option arguments) as const std::vector<std::string>& |
SetAllowUnknown(bool) |
Configure whether unknown options should cause parse failure |
GetUnknown() |
Get list of unknown options as const std::vector<std::string>& |
// Check if an option/flag was provided
if (parser.HasArg(OPT_port)) {
std::string value = parser.GetArgValue(OPT_port);
// use value...
}
// Check a flag
if (parser.HasArg(OPT_verbose)) {
// verbose mode is enabled
}
// Get positional inputs
const auto& inputs = parser.GetInputs();
for (const auto& input : inputs) {
std::cout << "Input: " << input << std::endl;
}
// Handle unknown options
parser.SetAllowUnknown(true); // Don't fail on unknown options
if (!parser.Parse(argc, argv)) {
// handle parse error...
}
const auto& unknown = parser.GetUnknown();
for (const auto& opt : unknown) {
std::cout << "Unknown option: " << opt << std::endl;
}
// Show help
parser.PrintHelp();- C++11 or later
- CMake 3.10+
- GCC or Clang
mkdir build && cd build
cmake ..
makemake testctest -R BasicParsing
ctest -R "ShortName|MultipleArgs"cpp-args/
├── cpp_args.hpp # Header-only library (all you need!)
├── example.cpp # Usage example
├── CMakeLists.txt # Build configuration
├── README.md # This file
└── test/ # Unit tests
├── test_basic.cpp
├── test_flag.cpp
├── test_allowed_values.cpp
└── ...
- Zero dependencies - Pure C++ standard library
- Compile-time safety - Options defined at compile time, enum-based access
- Type-safe - Use generated enum IDs instead of error-prone string literals
- User-friendly CLI - Accepts both
--log_lvland--log-lvl - Clean code - X-Macros eliminate repetition, single definition location
- Flexible - Mix options, flags, and joined options naturally in one macro
Restrict option values to a predefined set:
#define ARGS(F) \
F(format, f, "Output format", OPTION, {"json", "xml", "text"}) \
F(level, l, "Log level", OPTION, {"debug", "info", "warn", "error"}) \
F(library, L, "Link library", JOINED, {"cuda", "stdc++", "pthread"})
DEFINE_ARGS(App, AppTable, ARGS)Invalid values will cause parsing to fail with an error message.
Perfect for compiler-like interfaces where values are attached directly:
#define COMPILER_ARGS(F) \
F(library, L, "Link library", JOINED, {"cuda", "stdc++", "pthread"}) \
F(include, I, "Include path", JOINED, {}) \
F(define, D, "Define macro", JOINED, {}) \
F(output, o, "Output file", OPTION, {}) \
F(verbose, v, "Verbose mode", FLAG, {})
DEFINE_ARGS(Compiler, CompilerTable, COMPILER_ARGS)Usage:
g++ -Lcuda -I/usr/local/include -DNDEBUG -o app.out main.cppNon-option arguments are automatically preserved as positional inputs:
#define MY_ARGS(F) \
F(output, o, "Output file", OPTION, {}) \
F(verbose, v, "Verbose mode", FLAG, {})
DEFINE_ARGS(App, AppTable, MY_ARGS)
int main(int argc, char* argv[]) {
ArgumentParser parser(AppTable);
parser.Parse(argc, argv);
// Get positional inputs (files, etc.)
const auto& inputs = parser.GetInputs();
// inputs contains all non-option arguments
}# Everything that's not an option becomes an input
./app -o out.txt file1.txt file2.txt file3.txt
# inputs = ["file1.txt", "file2.txt", "file3.txt"]
./app file1.txt -v file2.txt -o out.txt file3.txt
# inputs = ["file1.txt", "file2.txt", "file3.txt"]Control how unknown options are handled:
ArgumentParser parser(Table);
// By default, unknown options cause parse failure
parser.Parse(argc, argv); // Returns false on unknown option
// Allow unknown options without error
parser.SetAllowUnknown(true);
if (parser.Parse(argc, argv)) {
// Parse succeeded even with unknown options
// Retrieve unknown options if needed
const auto& unknown = parser.GetUnknown();
for (const auto& opt : unknown) {
std::cout << "Unknown: " << opt << std::endl;
}
}Important: Unknown options must start with - or --. Arguments without these prefixes are treated as positional inputs, not unknown options.
// --fake-opt is unknown, "input" is treated as positional input
./app --fake-opt input.txt
# GetUnknown() returns ["--fake-opt"]
# GetInputs() returns ["input.txt"]The library automatically generates formatted help messages:
Usage Options:
--host, -H Server hostname
--port, -p Server port [Values: 8080|9090]
--library, -L Link library [Values: cuda|stdc++]
--verbose, -v Enable verbose mode
--help, -h Print help message
See unit tests in test/ directory for more comprehensive examples and edge cases.