A single-header, dependency-free C++ command-line argument parser.
Argparse-style ergonomics for C++ โ named & positional arguments, type casting, choices, value validators, defaults, auto-generated help, and Python-like keyword arguments in C++20.
Tutorial ยท Examples ยท Class reference
Why
Drop one header into your project and get a fully featured argument parser โ no
submodules, no CMake packages, no linking. Everything lives in argparse.h.
Features
- ๐งฉ Single header, zero dependencies โ just
#include "argparse.h". - ๐ฆ C++20 module โ optional
import argparse;viaargparse.ixx. - ๐ท๏ธ Named and positional arguments, freely mixed in any order.
- ๐ข Typed values โ
int,long long,double,bool, andstring. - ๐๏ธ Flexible arity โ fixed counts, or Python-style
'?'(zero-or-one),'*'(zero-or-more) and'+'(one-or-more), plus the matchingkZeroOrOneArgCount/kAnyArgCount/kFromOneToInfiniteArgCountconstants. - โ
Validation โ required/optional, value
choices, numericSetRange,SetPositive, filesystem checks (SetExistingFile), and customSetValidatorpredicates. - ๐ Variable binding โ
BindTo(&var)writes parsed values straight into your own variables. - ๐ฏ By-name getters โ
obj.GetAsInt("count")straight off the parsed result. - ๐ Auto-generated help & usage, with custom epilogue and overridable usage line.
- ๐ค Long-option abbreviations (
--verbโ--verbosewhen unambiguous). - โ๏ธ Configurable โ custom prefix characters, ignore-unknown args, custom namespace.
- ๐ Three declaration styles, including Python-like keyword arguments in C++20.
- ๐งช Tested across Linux, macOS and Windows on C++17/20/23.
Quick start
Copy argparse.h into your project โ that's the whole installation.
#include <iostream> #include "argparse.h" int main(int argc, char** argv) { auto parser = argparse::ArgumentParser("greet").SetDescription("A tiny greeter"); parser.AddArgument(argparse::CreateNamedArgument("n", "name", 1, argparse::ArgTypeCast::e_String, true).SetHelp("Who to greet")); parser.AddArgument(argparse::CreateNamedArgument("c", "count", 1, argparse::ArgTypeCast::e_int, false).SetDefault(1).SetHelp("How many times")); auto obj = parser.ParseArgs(argc, argv); if (!obj.IsArgValid()) { std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl; return 1; } auto name = obj.GetArg("name"); for (int i = 0; i < obj.GetArg("count").GetAsInt(); ++i) std::cout << "Hello, " << name.GetAsString() << "!\n"; return 0; }
$ c++ -std=c++17 greet.cpp -o greet $ ./greet --name World --count 2 Hello, World! Hello, World!
Three ways to declare an argument
Pick whichever reads best โ they build the same argument.
// 1. Fluent setters parser.AddArgument(argparse::CreateNamedArgument() .SetShortName("n").SetLongName("numbers") .SetType(argparse::ArgTypeCast::e_int) .SetAnyNumberOfArgumentsButAtLeastOne()); // 2. Positional factory arguments parser.AddArgument(argparse::CreateNamedArgument("n", "numbers", argparse::kFromOneToInfiniteArgCount, argparse::ArgTypeCast::e_int)); // 3. Keyword style โ Python-like, needs C++20 designated initializers parser.AddArgument(argparse::CreateNamedArgument({ .shortName = "n", .longName = "numbers", .nargs = argparse::kFromOneToInfiniteArgCount, .type = argparse::ArgTypeCast::e_int}));
Note: every argument is required by default โ call
SetRequired(false)(or setrequired = false) to make one optional.
Short names may be written as a single char ('n') as well as a string
("n"), and arity accepts a Python-style char in place of a constant:
// 'c' short name, '?' == zero-or-one value (same as kZeroOrOneArgCount) parser.AddArgument(argparse::CreateNamedArgument('c', "count", '?') .SetType(argparse::ArgTypeCast::e_int).SetDefault(1)); // '+' == one-or-more, '*' == zero-or-more parser.AddArgument(argparse::CreatePositionalArgument("files", '+'));
Binding values to variables
Instead of pulling each value out with GetArg(name).GetAsX(), you can bind an
argument directly to one of your variables with BindTo(&var). After a
successful ParseArgs(), the parsed value is written straight into it.
int count = 1; // initial value doubles as the default std::string name; std::vector<int> ids; auto parser = argparse::ArgumentParser("prog"); parser.AddArgument(argparse::CreateNamedArgument("c", "count", 1).BindTo(&count)); parser.AddArgument(argparse::CreateNamedArgument("n", "name", 1).BindTo(&name)); parser.AddArgument(argparse::CreateNamedArgument("i", "ids") .SetAnyNumberOfArgumentsButAtLeastOne().BindTo(&ids)); auto obj = parser.ParseArgs(argc, argv); if (obj.IsArgValid()) { // count, name and ids are already populated โ no GetArg(...) calls needed. }
BindTo works for every supported type (bool, int, long long, double,
std::string) and their std::vector<> variants, and is available in C++11
onward.
- Type is inferred from the bound variable, so you don't need a separate
SetType()call (and shouldn't add one that contradicts it). - The bound variable must outlive the
ParseArgs()call. - If an optional argument is absent (and has no default), its bound variable is left untouched โ so its initial value acts as the default.
- Bindings are applied only on a successful parse; a failed parse never writes through them.
Validating values
Beyond choices, an argument can carry a validator that each parsed value must
pass; a failing value makes ParseArgs return an invalid result with a message.
// Numeric range (also sets the type for you) parser.AddArgument(argparse::CreateNamedArgument('p', "port") .SetRange(1, 65535)); // Strictly positive parser.AddArgument(argparse::CreateNamedArgument('r', "ratio") .SetType(argparse::ArgTypeCast::e_double).SetPositive()); // Filesystem checks (available when <filesystem> is, i.e. C++17+) parser.AddArgument(argparse::CreateNamedArgument('i', "input") .SetExistingFile()); // Any custom predicate, with an optional error message parser.AddArgument(argparse::CreateNamedArgument('m', "mode") .SetValidator([](const std::string& v){ return v == "fast" || v == "safe"; }, "mode must be 'fast' or 'safe'"));
Built-in validators include SetRange (int / long long / double, as (lo, hi)
or (max)), SetPositive, SetNonNegative, and the path checks
SetExistingFile, SetExistingDirectory, SetExistingPath,
SetNonexistentPath.
Mixing positional and named arguments
Positional and named arguments can be declared and passed in any order โ the parser assigns bare values to positionals left to right while pulling named options out of the stream.
auto parser = argparse::ArgumentParser("cp"); parser.AddArgument(argparse::CreatePositionalArgument("src")); parser.AddArgument(argparse::CreatePositionalArgument("dst")); parser.AddArgument(argparse::CreateNamedArgument('f', "force", 0, argparse::ArgTypeCast::e_bool, false)); // a flag // all equivalent: a b --force | --force a b | a --force b auto obj = parser.ParseArgs(std::vector<std::string>{ "a", "--force", "b" }); std::string src = obj.GetAsString("src"); // "a" std::string dst = obj.GetAsString("dst"); // "b"
Reading results by name
After a successful parse you can read values straight off the result by
argument name, without going through GetArg(...) first:
int n = obj.GetAsInt("count"); double ratio = obj.GetAsDouble("ratio"); std::string name = obj.GetAsString("name"); const std::vector<int>& nums = obj.GetAsVecInt("numbers");
The longer obj.GetArg("count").GetAsInt() form still works and is handy when
you want to inspect the Argument itself (e.g. GetArgumentExists()).
Configuring the parser
ArgumentParser can be built from a ParserSpec aggregate, which collects the
parser-level options in one place (the counterpart of the keyword-style argument
specs):
auto parser = argparse::ArgumentParser(argparse::ParserSpec{ .name = "cptool", .description = "Copy files", .prefixChars = '-', .addHelp = true, .allowAbbrev = false});
The designated-initializer form needs C++20; the same struct also works with ordinary aggregate initialization in C++11/14/17.
Using it as a C++20 module
On toolchains that support C++20 modules (MSVC, GCC โฅ 14) you can consume the
library through import instead of #include:
import argparse; int main(int argc, char** argv) { argparse::ArgumentParser parser("demo"); // ... same API as the header ... }
Add argparse.ixx to your build as a module interface unit; it wraps
argparse.h and re-exports the public API. The header remains fully usable on
its own, so nothing changes for #include users. Define
ARGPARSE_NAMESPACE_NAME when building the module to rename the exported
namespace, exactly as with the header.
Requirements
- A C++11 compiler or newer. The library is continuously tested on C++17, C++20 and C++23 across Linux, macOS and Windows.
- The
std::any-basedGet()accessor is available from C++17 onward; the typed getters (GetAsInt(),GetAsVecInt(), โฆ) work in every standard. - Designated-initializer (keyword) argument construction needs C++20.
Building the tests
The tests live under tests/ and are never included by the header, so consumers
can ignore them entirely.
cmake -S tests -B build cmake --build build ctest --test-dir build --output-on-failure
Override the standard with -DCMAKE_CXX_STANDARD=20 (or 23).
Documentation
- ๐ Tutorial โ step-by-step introduction.
- ๐ Examples โ flags, choices, positional/named mixes, bool, ranges, keyword args, and more.
- ๐ Class reference โ full API overview.
- ๐ About โ project background.
License
Released under the MIT License โ see the header of
argparse.h for the full text. ยฉ 2021 simfeo.