# args: a single-header C++11 argument parser with argparse-shaped thinking

> args is a header-only C++11 library for parsing command line arguments, designed to look somewhat like Python's argparse while adding static type checking and fully nestable groups. Its creator has declared it feature-complete, so the interesting question for an adopter is what it does well and where the argparse resemblance stops.

**Taywee/args** —   A header-only C++ argument parser library. Supposed to be flexible and powerful, and attempts to be compatible with the functionality of the Python standard argparse library (though not necessarily the API).

- Repository: https://github.com/Taywee/args
- Website: http://taywee.github.io/args/
- Stars: 1,642 · Forks: 187
- Language: C++
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/taywee-args

## One header, a stated end state, and an argparse-shaped API

args is a simple, small, flexible, single-header C++11 argument parsing library, and the first thing the README does is set expectations about its future.

This library is considered feature-complete by its creator. It will still receive bug fixes, and good pull requests will be accepted, but no new major functionality or API changes will be added to this codebase.

That statement matters more than a feature list, and it is the kind of thing a maintainer says when the design is settled and the remaining work is maintenance. For an adopter it converts an open question into a known quantity. You are not adopting a project that is about to reinvent its parser; you are adopting a finished design with a stable API and a bug-fix commitment.

The design goal is stated next. It is designed to appear somewhat similar to Python's argparse, but in C++, with static type checking, and hopefully a lot faster, also allowing fully nestable group logic, where Python's argparse does not. The repository topics list cpp11 and cxx, so the C++11 floor is real and the library targets the older standard deliberately, which is what makes a single-header dependency attractive.

The licence is MIT and unusually permissive in practice: you can use the header as-is with no restrictions. The only requirement is that you don't remove the licence and the author's name from the header of the args.hxx file in source redistributions, in other words don't pretend that you wrote it. Attribution in a README or man page is welcome but not required.

The last push was on 2026-10-05, the repository is not archived, and the release history shows 6.6.0 on 2026-07-22, 6.5.0 on 2026-07-15 and 6.4.16 on 2026-05-07. Patch-level releases within a 6.x line, consistent with a project that is being maintained rather than extended. The API documentation is at taywee.github.io/args.

## What the parser does, in the maintainer's own list

The README has a section titled What does it do, and the list is worth walking through because each item is a design decision.

You can handle flags, flag+value and positional arguments simply and elegantly, with the full help of static typechecking. You can use your own types in a pretty simple way. You can use count flags, and lists of all argument-accepting types.

You get full validation of groups of required arguments, though output isn't pretty when something fails group validation, and user validation functions are accepted. Groups are fully nestable. Help is generated for you, with some good tweakable parameters. You can customize all prefixes and most separators, allowing you to create an infinite number of different argument syntaxes.

By default the library parses any type that has a stream extractor operator for it, and if that does not work for your uses you can supply a function and parse the string yourself. You can decide not to allow separate-argument value flags or joined ones, such as disallowing `--foo bar` and requiring `--foo=bar`, or the inverse, or the same for short options. You can create subparsers, reuse arguments for multiple commands, and refactor your command's logic into a function or lambda. One value flag can take a specific number of values, so `--foo first second` slurps both arguments. Value flags can optionally accept values. And there is autocompletion for bash.

Two of those are the ones that separate args from a hand-rolled parser. The first is the custom value type support, which combined with static type checking means the value arrives in your program as the type you declared, not as a string you have to convert. The second is the subparser support, which is the feature that turns a parser into a command line application, and the fact that the library supports reusing arguments across multiple commands is what makes a git-style or docker-style interface expressible without duplicating flag definitions.

The prefix customisation deserves one sentence of its own, because it is the difference between fitting a library and forcing your interface. Customising all prefixes and most separators means the library can present `--flag`, `-f`, `/flag` or anything else you want, so an existing convention in a codebase is not a reason to write your own parser.

## What it will not ever do, and the price of that

The README has a section titled What does it not do, followed by one that says it will not ever. This is unusually explicit, and the three items are the terms of adoption.

The first is that you cannot intermix multiple different prefix types, so `++foo` and `--foo` in the same parser is out, though shortopt and longopt prefixes can be different from each other. The reasoning is straightforward, since the parser would have no way to tell which prefix scheme a given token belongs to, and an ambiguous scheme is a source of bugs that a type checker cannot catch.

The second is that flags cannot be made sensitive to order, in the manner of GNU find, nor sensitive to relative ordering with positionals. The only orderings that are order-sensitive are positionals relative to one another, and list positionals or flag values relative to their own respective items. The reason is the same in both cases: the library assigns as it goes rather than sorting first, and that is what makes static checking of the positional assignments possible.

The third is the one with the longest explanation, and it is the most likely to surprise you. You cannot use a positional list before any other positionals, because the last argument list will slurp all subsequent positional arguments. The maintainer gives the reason: the logic for allowing it would be a lot more code, and would make static checking much more difficult, requiring sorting std::string arguments and pairing them to positional arguments before assigning them, rather than the current approach of assigning them as we go for simplicity and speed. The library doesn't stop you from trying, but the first positional list will slurp in all following positionals.

That last one is the design philosophy of the whole library stated plainly. args chooses the simpler and faster assignment model, and accepts a limitation in exchange. The same trade explains the group validation output problem, which the README addresses in its own section with the heading Group validation is weird. How do I get more helpful output for failed validation? The answer given is that this is unfortunately not possible, given the power of the groups available, because a validation like `(A && B) || (C && (D XOR E))` evaluates to false as a whole without the library being able to say which part was wrong, and that is doubled by validation operations being ordinary functions without special meaning to the library.

For a user, that means the error message on a failed group is a failure, not a diagnosis. You will be writing your own clearer messages if that matters, which for a user-facing tool it usually does.

## Installing a single header, and the packaging options around it

The installation story is unusual for a C++ library, because there is nothing to compile and nothing to link.

```shell
sudo make install
```

Or, to install it somewhere special, with the default being /usr/local:

```shell
sudo make install DESTDIR=/opt/mydir
```

Looking at the Makefile, install creates the include directory under DESTDIR and copies args.hxx into it, and uninstall removes it again along with the man pages. That is the whole build. The default target is different though, and it is worth knowing: the Makefile's runtests target configures with cmake, builds with cmake --build build --parallel and runs ctest --output-on-failure, so the test suite goes through CMake and CTest rather than a bespoke runner.

The README also says you can copy the file into your source tree, if you want to be absolutely sure you keep a stable API between projects. For a project that has declared itself feature-complete, that is a reasonable choice: vendoring one header removes the question of what a dependency upgrade will do to your build.

The man pages are a separate target:

```shell
make doc/man
sudo make installman
```

which requires Doxygen, and the local Doxygen documentation is generated with `doxygen Doxyfile`, after which the docs are in doc/html. The installman target bzip2-compresses the generated man3 pages and installs them under share/man/man3.

Beyond the Makefile, the repository supports a wide range of build systems, and the README says many standard ones are available and included: CMake, conan, buck and meson, with an example CMake file using args in the examples directory. The tree confirms it, with CMakeLists.txt at the root, a conanfile.py, a BUCK file with a .buckconfig, meson.build with meson_options.txt, a .tipi/ directory, a .ycm_extra_conf.py for YouCompleteMe, and a .gitmodules. There is also a fuzz/ directory, which is a good sign for a parser, since the input is untrusted and a fuzzer is how you find the crash.

The tipi.build instructions are a small JSON file in .tipi/deps:

```json
{
    "taywee/args": { "@": "6.4.1" }
}
```

and you can optionally remove the @ section to target HEAD easily, which is a warning as much as a convenience.

## The API, and what the tests double as

The usage model has four steps. Create an ArgumentParser, modify its attributes to fit your needs, add arguments through regular argument objects or create your own, and match them with an args::Matcher object. Then you call either args::ArgumentParser::ParseCLI for the full command line including the program name, or args::ArgumentParser::ParseArgs with just the arguments to be parsed. The argument and group variables can then be interpreted as a boolean to see whether they have been matched, and all variables can be pulled, including the boolean match status for regular args::Flag variables, with args::get.

The matcher as a separate step is the piece that differs from argparse's model, and it is the piece that makes the static typing work. A flag is declared with its type, and the matcher ties the parsed result to the variable, so a mismatched type is a compile error rather than a runtime surprise.

The test suite is described in a way that is worth borrowing. You can find all the test cases in the test directory, and each test is a small self-contained .cxx file with its own main(), so they double as focused usage examples. That is a genuinely good pattern for a parsing library, because the test for the slurping positional behaviour is also a complete program you can compile and read to understand the behaviour. The examples directory reinforces it, with completion.cxx and gitlike.cxx alongside a bash_completion.sh, which is the bash autocompletion feature made concrete.

The repository also carries a CHANGELOG, a CONTRIBUTING.md, a Doxyfile and a LICENSE, and the Doxyfile is what the pages target uses, with the Makefile's pages target running doxygen and copying the output into pages/ for the GitHub Pages site.

The CHANGELOG is worth checking before you pick a version, because the release numbering shows 6.4.16, 6.5.0 and 6.6.0 in quick succession, which is the pattern of a maintained project fixing things and adding small things within a stable major line.

## Where args is the wrong choice

There are three limits, and they come from the same place, which is the commitment to C++11 and to a single header.

The first is the C++11 floor and what it costs. The library is deliberately built on the older standard so it can be a drop-in header anywhere, and that means the type system it offers is the type system of 2011. A modern compiler in a modern project is mostly interested in C++17 or C++20 features, and the argument parser cannot use them, so a project that wants its CLI options expressed with, say, a std::variant of several types, or with constexpr parsing, or with structured bindings in the surrounding code, will find itself writing code in an older style around it. That is a consistency cost more than a capability cost.

The second is the diagnostic quality, which the README states plainly. Group validation gives you a pass or fail, not a reason. For a developer-facing tool where the user typed a flag wrong, the error message is the product, and a tool that says group validation failed without saying which condition is the wrong one pushes the work back onto the user. The same applies to the positional slurping behaviour, which produces a confusing result rather than an error.

The third is the feature-complete status itself. If your roadmap includes command line features that are not in the library now, they will not arrive from upstream, because the creator has said no new major functionality or API changes will be added. You would be forking the library, and a fork of a 6.x header with a stable API is a reasonable thing to own, but it is a fork, with all that implies.

There is also the comparison to make with the alternatives in the C++ ecosystem. The well-known ones, the Boost program_options library and the CLI11 library, are the natural reference points, and both are actively developed and neither is limited by a feature freeze. If what you need is a maintained option with a large option set, a library that still takes new features is the better default, and args is the one to reach for when you want a single header, C++11 compatibility, and an API that reads like argparse.

## Conclusion

Adopt args if you want a self-contained C++ argument parser with type-safe values, nestable validation groups and subparsers, and you are prepared to write your own help text wording rather than expect a framework to generate a UX for you. Do not adopt it expecting argparse's error messages, because the library's own documentation says group validation output is not pretty. Verify first that your C++11 compiler and standard library combination handles the types you intend to parse, that the flags you need fit the prefix model, since intermixing prefix types is not supported, and that the feature-complete status is acceptable for your roadmap.

## FAQ

### Is the args C++ argument parser still being developed?

The README states the library is considered feature-complete by its creator, and that it will still receive bug fixes and good pull requests, but no new major functionality or API changes will be added. The last push was on 2026-10-05, and recent releases include 6.6.0 and 6.5.0 in July 2026.

### How do I install the args header-only library?

Run `sudo make install`, which copies args.hxx into the include directory under /usr/local, or `sudo make install DESTDIR=/opt/mydir` to place it elsewhere. The README also says you can copy the file into your source tree, and that CMake, conan, buck and meson support are included.

### What does the args library not support?

The README says it will never allow intermixing multiple different prefix types, such as ++foo and --foo in the same parser, will not make flags sensitive to order, and will not let you use a positional list before any other positionals, because the first positional list will slurp all following positionals.

### Why are the args error messages unhelpful for group validation?

The README explains that a validation such as (A && B) || (C && (D XOR E)) only tells the library that the expression evaluated false, not which part was wrong, and that this is doubled by validation operations being ordinary functions with no special meaning to the library. The answer given is that more helpful output is unfortunately not possible.

## Sources

- [License: MIT](https://github.com/Taywee/args/blob/master/LICENSE)
- [Project website](http://taywee.github.io/args/)
- [README](https://github.com/Taywee/args/blob/master/README.md)
- [Releases](https://github.com/Taywee/args/releases)
- [Taywee/args on GitHub](https://github.com/Taywee/args)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/taywee-args
