TCLAP for command line argument parsing in C++

A long while ago I was looking for a way to handle command line arguments in a C++ program I was writing for Windows. At the time I only found Boost.Program_options. After a bit of experimenting I found that the pre-built Boost libs I found back then had some issues and after a bit of thinking I decided to write the program in Python instead :) Now I once again find that I need to handle command line arguments in C++ on Windows, but this time I found quite a few options, gflags, ezOptionParser, TCLAP. They are all stand-alone, meaning that I don’t need to pull in a huge dependency like Boost or Qt, and liberally licensed (BSD3 and MIT) so usage in at work is no problem. After a bit of playing with all three I found that TCLAP is most to my liking, but gflags does have one nice little feature–it allows putting command line option definitions pretty much anywhere in the code. This would solve one of the problems I’m facing; the tool shall consist of a main program and pluggable modules, where each module must be able to add command line arguments of its own. However, it turns out that the TCLAP API easily allows implementing such a scheme. Here’s how I implemented it in the experiment code I’ve been playing with this weekend.

How it works

The basic idea is to create a central instance of TCLAP::CmdLine that can be used to register options with in the program and the pluggable modules. By declaring the option instances as top-level variables in compilation units it will be enough to just load a pluggable to the constructors run, and by passing the central TCLAP::CmdLine to the constructors they will register themselves properly. This seems to be same idea used in gflags.

Registering a command line option

The following code is the code my pluggable module used to register an option:

1
2
3
4
5
6
7
TCLAP::ValueArg<int> value("v",
                           "value",
                           "ONE: the value to return",
                           false,
                           42,
                           "integer",
                           argparse::argparser);

The parser

I put the parser in its own namespace, and due to the API of TCLAP::CmdLine I found that I needed to subclass it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <tclap/CmdLine.h>
 
namespace argparse {
 
class ArgParser : public TCLAP::CmdLine {
public:
    ArgParser() : TCLAP::CmdLine("No message set.", ' ', "No version set.") {}
 
    void setMessage(std::string msg) { _message = msg; }
    void setVersion(std::string version) { _version = version; }
};
 
extern ArgParser argparser;
 
}

The main function

After this I just need to instantiate the central parser instance and set it up.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
argparse::ArgParser argparse::argparser;
 
int main(int ac, char **av)
{
    argparse::argparser.setMessage("The message.");
    argparse::argparser.setVersion("0.0.0");
 
    // load the the plugin modules
 
    argparse::argparser.parse(ac, av);
 
    // do the work
 
    return 0;
}

Limitations

The main limitation I can see with this approach, and AFAICS that would be true with gflags as well, is that it’s not very easy to find the plugin modules to load by passing them on the command line. If the TCLAP API allowed parsing without acting on --help and --version, as well as be forgiving with arguments it didn’t know how to handle, it would be possible to use an option in the main application to find the modules, load them, and then re-parse the command line. In my particular case it doesn’t matter much, the plugin modules will all be found in a well-known place and all available modules should be loaded every time. It does make testing a bit more cumbersome though.

Share

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>