Parsing program options, most of us re-invented that wheel, or at a given moment we stopped doing that and for example switched to the boost libraries.

We define some option “–foo value” (or things like –foo=value …), we loop over the program arguments, try to see if they contain our –foo, parse the next one, and store the value somewhere, in a variable called foo, or in a structure having a field called foo. Let’s assume we would store it in a structure.

Time passes by and suddenly we come to the conclusion we need yet another option, say bar. We extend the structure and write extra code for trying to parse a –bar and its value, and store it in the new bar member of the structure.

Time passes by and …

What if we could automate this somehow ?

Idea:

  • struct of the options
  • add a new option ‘foo’ in the struct, don’t write new code, and it parses “–foo value” automagically

Sure we will need to write code, but what if writing this parsing code is only done once, and from that moment, it is just nothing more than adding an extra member to the options struct …

Spoiler alert, this is possible with reflection 🙂

Options structure

Our toy example will be:

On the command line, one can register/specify a person buying shares, when no name is provided (–name), we will call that person “Front”, and when no amount of shares is mentioned, the default value will be “242”.

Note we do allow, upon registration, to not buy any shares yet, by specifying the value of 0. When we specify an option which does not exist (eg. “–nope”) it is ignored.

The algorithm

  • loop over all the members of the Options struct
  • see if the command line options contain an entry –<name of the member> , and we need this not to be the last option (otherwise a value would be missing), and this value should be able to fill in a variable of the correct type (aka the type of the member)
  • when an option is not specified, it will be on the default value of the member in the struct (eg: 242 shares)

The command line options

Let’s just store these in a vector of string views

That was easy.

Parsing function

Let’s be generic, so templatize it on the type of the Options structure, and let’s not limit ourselves to a vector, let’s use a span of command line options.

What does it return, an instance of the Options struct.

The most simple implementation of that (TDD rings a bell …)

This gives at this moment the following output when we don’t specify any command line options, or correct options, or incorrect options.

Front
242

Implement the algorithm

Let’s start by looping over the members, this is a trick we already know.

See if the options contain our –NameOf(member).

NameOf is in our reflection toolset spelled out as “identifier_of“.

As said, if it is not found, let’s continue with the next member:

otherwise let’s check if there is still a next command line option, which should specify the value. If that is not the case, let’s be angry and just exit (throwing could also be an option).

otherwise let’s try read out the value (let’s use an input string stream for that) into the according member value (for that we need to splice, we need to get back to the code domain: opts.[:member:]), and check if that went ok or not. In case it is not ok, we will become angry again.

Note, we are using display_string_of() here, another method in the reflection toolbox.

If that went ok, continue to loop, and process the next member of the Options struct.

Let’s put this all together:

Let’s see it live at : https://compiler-explorer.com/z/qTv7svfx4

Some tests with different command line options, let’s see what there output is:

this --name Level is a --shares 42
Level
42

this --name Level is a --shares foo
Failed to parse option --shares into a int

--name Level --shares 42
Level
42

--name Level --oops 42
Level
242

Can we do better ?

Imagine we would like to know, explicitly if no “volume of shares” was specified, instead of assuming this. Currently all we know is the value is 242, which is a strong indication, but maybe the user did explicitly specify 242.

And let’s assume we would like to know that for every field.

The key to success: std::optional<T>, meaning we turn our Options struct into:

And we could inspect this later on and drive our logic.

Challenge:

We should no longer read in the member type from the input string stream, since that would give std::optional<T>, we need T.

How do we get the type T out of the reflected member:

We parse it in a local variable, which we can assign to the (std::optional) member:

Let’s turn our main into:

We can play with it live at: https://compiler-explorer.com/z/djredKfP4

Some input -> output:

--name Level --shares 42
Level
42

--name Level
Level

--name Level --shares 242
Level
242

identifier_of versus display_string_of

Without going into detail about this, have look here to see the differences on some types: https://compiler-explorer.com/z/jT3bqna17

Conclusion

In several uses cases the command line parsing is not that advanced, and simple solutions can be used. With c++26 and reflection, these solutions can be “write once”, and scale/extend without problems and little changes.

How neat is this. The power of reflection …


Leave a Reply

Your email address will not be published. Required fields are marked *