I wrote my own argument parser in C++20
Well, it kind of happened by accident, if I’m honest. While working on a
different project, where I’m building a set of utilities (assembler,
disassembler, debugger, simulator) for a custom programming language, I needed
a parser to create some basic CLI interfaces. I wanted to limit the amount of
dependencies and thus didn’t want to reach for Boost’s
Another reason is that I don’t really like it that much.
syntax is… weird, confusing and a bit cumbersome. It’s not something I can
use without spending some time with the documentation.
On the other edge of the spectrum there’s the venerable
getopt family of
APIs. Which does the job… but feels dated and I was hoping for something
more along the C++20 lines. Therefore, I decided to get my hands dirty and
quickly cobble something together that would satisfy my requirements.
Initially, having the ease of use in mind as a priority, I wanted to have
something along the lines of Python’s
argparse.ArgumentParser. One thing
that doesn’t translate well from Python to C++ is the way
the parsing results. In case of Python it’s an dictionary-like object. To do
something similar in C++, I’d probably need some sort of wrapper types or use
std::any or something similar. I’ve decided to do something similar to
program_options (ironically) and bind variables to CLI argument definitions. In such case
it’s easy to retrieve the values and at the same time provide the defaults.
So, it happened. I’ve spent maybe 12h in total, spread across a couple of days,
writing version v0.1.1. So far, I’m quite happy with the project. The
initial goals have been fulfilled completely.
ArgParser is something I can
just pick up and integrate in any project in a matter of minutes. The
supported feature set is maybe insufficient for the moment to compete with
Goliaths such as
program_options but that is fine - it was never the intention.
What can it do?
The feature I’m most satisfied with is automatic conversion to given type. If
the argument is bound to an
int, the parser will convert the CLI value to an
int or raise
ArgConversionEx if it’s impossible. Same applies to any other
supported data types. It even detects narrowing type conversion problems i.e.
The following invocation will produce
ArgConversionEx, like so:
$ myprogram 256 '256' overflows
At the moment the parser supports the most basic PODs:
std::vectorof any of the above
std::vector is special. If an argument is bound to
semantics is changed. It becomes cumulative and may occur more than one time
on the command line. All the values from the command line will be collected in
the bound vector variable (of course conversion to the destination type will be
performed as well). For example, given:
myprogram -p /bin --path /usr/bin --path /usr/local/bin
Will result in
paths vector being populated with all collected command line
option values. As mentioned, it even performs conversion on vector types:
myprogram 1 2 3 4
Will produce a vector of integers: 1,2,3,4.
Which I think is pretty cool and quite convenient at the same time.
How does it work?
The majority of the code is pretty straight forward. The only bit that is slightly more complex is related to variable binding.
Converting arguments from strings to (almost) any types
The type of conversion required is selected dynamically using the type provided
Argument::set() API. Let’s first consider how type dependant conversion
can be implemented. For that, I’m gonna declare a
I think it’s becoming pretty apparent now that I’m gonna use template specialisation for that. Let’s have a look on strings first:
This specialisation will be selected if a
std::string is constructible from
the given type. For any other types, the
Converter class remains undefined.
Let’s have a look on another specialisation for
Having these two specialisations, it’s now possible to use them like so:
Storing the reference to the bound variable is the other missing piece of the
puzzle. To achieve that, I’m doing something very similar as what
does. The reason why I don’t use
std::any in the first place is that I need
to combine it with the type converter.
I need a base type to perform type erasure. For that, I’m declaring something similar to the following interface:
Now, a templated child class is needed to actually store the references to variables:
Now, I’ve got the “type independent” base type, I can just store it in a container i.e.:
… and thanks to polymorphism:
ArgParser’s case, there’s some other elements involved but they are
irrelevant. The principle remains the same.
ArgParser on Gitlab
Feel free to checkout the project on gitlab. Maybe, you’ll find it useful for yourself. I’m open to pull requests as well so, if you find a fundamental problem or would like to contribute, feel free to do so.