Program options

We all know that a C/C++ application could get input values from the environment through the argc/argv variables passed to its main() function. And we all know how boring is checking and making available for use these values.

The good news is that we could use a Boost library, Program Options, and let it do large part of the job for us.

Let's say that we have an application that should get from command line an integer representing a size:
myApp -s 42
We want also to provide a way for the user to know how to call it:
myApp -h
And we want to be allowed to enter the parameters in a more verbose way, --size and --help.
If the user calls the application without specifying any parameter we would complain, and show him the help.

Naturally, we should check that nothing weird happens, like duplicated option tags or unexpected one are passed. And we should check the option consistency, we want to ensure that the user passes an integer (and not a floating point, or a string) as size.

Using Boost Program Options all of this comes at a reasonable price. We just have to include the required header, and maybe provide a shortcut for the namespace:
#include <boost/program_options.hpp>
namespace po = boost::program_options;
Then we provide a description for the options we accept:
po::options_description od("Available options"); // 1.
od.add_options()
("help,h", "help message") // 2.
("size,s", po::value<int>(), "max size"); // 3.
1. The options_description object is created with a description - try to think of something that could be result useful for the application user.
2. Options could be added in a number of way. Here we see how to specify two tags, --help and -h, and an associated description. No argument value is expected in this case.
3. Size option tag should be followed by an integer representing the size value. The second parameter is passing this information to the options_description object.

If the option "size" would default to 42, we can enforce modifying in this way its definition:
("size,s", po::value<int>()->default_value(42), "max size");
Our code will access the values through a map where key is the option name (here "help" or "size") and the value a boost::any object. And here it comes the tricky part. We have to call three Boost Program Options functions to load into the map the values: parse_command_line() that matches the argv values with the expected options as stored in the option_description; and then store() and notify() to complete the job.

If the user has entered something conflicting with the rules set in options_description, parse_command_line() throws an error, that we should be ready to catch.

The resulting code should look more or less like this:
po::variables_map vm;
try
{
   po::store(po::parse_command_line(argc, argv, od), vm);
}
catch(const std::exception& ex)
{
   std::cout << "Error checking program options: " << ex.what() << std::endl;
   std::cout << od << std::endl;
   return 1;
}
po::notify(vm);
The user input now is in the variables_map, and we just have to implement our logic.

So, we could ensure at least an option has been passed to the application checkin the map size:
if(!vm.size())
{
   std::cout << "Please specify an option" << std::endl;
   std::cout << od << std::endl;
   return 2;
}
We can check if an option has been selected by the caller verifying that the count for an option is not zero. In case of the "help" option, we usually just dump the options description and terminate:
if(vm.count("help"))
{
   std::cout << od << std::endl;
   return 0;
}
To extract the associated value from an option, we should specify the actual type stored in the boost::any object (beware of wrong types - they would lead to an exception):
if(vm.count("size"))
{
   std::cout << "The passed size is " << vm["size"].as<int>() << std::endl;
}
The code in this post is based on the getting started example that you can find in the official Boost Program Options tutorial.

No comments:

Post a Comment