Leverging X-Macros and Boost to Simplify Command-Line Options

| | 3 Comments

A traditional library for command line processing is the getopt library. For C++ programmers, a powerful alternative is the Boost::Program_options library (PO). PO allows you to specify your options in a rather simple and flexible way:

po::options_description desc("Allowed options");
desc.add_options()
    ("help", "produce help message")
    ("compression", po::value<int>(), "set compression level")
;

What I like about OI is that it does all the heavy lifting for you, like putting the command-line options into variables:

desc.add_options()
    ("ouput-file,o", po::value<std::string>(&arg_output_file)->default_value("out.txt"), "Save output to file.")
;

Now you can access your program and pass command line options to it:

foo
foo --output-file=foo.out
foo -o foo.out

And after the application processes your command line options, the values of output-file will be found in the arg_output_file variable. Now you just have to create a bunch of arg_ variables, and then pass them to add_options. However, this quickly becomes hard to maintain when you realize that to add a new command line options, you may have to edit multiple files to get it to work right. This is where the awesome power of X-Macros come into play.

X-Macros are undefined macros that are contained in a file, say app.cmds:

//XCMD(lname, sname, desc, type, def) 
XCMD(help,             h, "display help message", bool, false)
XCMD(version,          v, "display version information", bool, false)

One uses these X-Macros by defining XCMD in a source file, including app.cmds, and then undefining XCMD. By defining XCMD different ways, you can reuse your cmds file.

#define XCMD(lname, sname, desc, type, def) type arg_##lname ;
#include "app.cmds"
#undef XCMD

po::options_description desc("Allowed options");
desc.add_options()
#define XCMD(lname, sname, desc, type, def) ( \
    #lname "," #sname, \
    po::value< type >(&arg_##lname)->default_value(def), \ 
    desc )
#include "app.cmds"
#undef XCMD
;

This code would become the following after preprocessing the X-Macros.

bool arg_help;
bool arg_version;

po::options_description desc("Allowed options");
desc.add_options()
    ("version,v", po::value< bool >(&arg_version)->default_value(false) , "display help message")
    ("help,h", po::value< bool >(&arg_help)->default_value(false) , "display version information")
;

Now to update your command line options, all you need to do is edit your commands line file, and PO and X-Macros will do the rest for you. Isn’t that totally cool!

I’ve uploaded a simple application framework that makes use of these concepts. I’ve used it in my recent application development, including the 1.2 version of Ngila, and three other unreleased projects. Click here to download the framework. You will of course need to install Boost to use it.

The X-Macros in this framework are a bit more complicated than the examples here because they handle conditions not discussed here. These include an optional sname argument, and the ability to handle compound command line options, e.g. arg_output_file being specified by --output-file instead of --output_file. I use parts of the Boost::Preprocessor library to support these things. The framework also uses additional features of Boost::Program_options, including passing arguments via a file and using positional arguments.

Try it out and let me know what you think.

3 Comments

It looks good, but wouldn’t popt be easier to distribute?

I’ve never used popt or know anything about it. I use other parts of the Boost library, so switching to popt would not be of much help. Of course, one could adapt these techniques to popt or getopt.

About this Entry

This page contains a single entry by Reed A. Cartwright published on March 25, 2008 8:00 AM.

Research Blogging Roundup was the previous entry in this blog.

FBI Arresting People for Visiting their Websites is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Archives

Powered by Movable Type 4.37