---
title: Boost program_options
url: https://www.grahambrooks.com/post/2013-12-06-boost-program_options/
date: 2013-12-06T00:00:00+00:00
author: Graham Brooks
tags:
- C++
- Testing
---

# Boost program_options

I have been spending time recently writing command line apps in
C++11. Each time I wanted a way of handling command line arguments
flexibly. I chose to use the boost::program_options library. The
documentation is pretty good but there are some assumptions (aliased
namespace) and the example code is broken up with paragraphs of text
explaining what the code does.

When I start to explore an API to a library I start by writing
tests. After working with the program_options library on a few small
applications the same pattern came up each time so I thought I would
share the results as a complete app, although admittedly the app does
nothing other than accept command line parameters.

The full source code can be found at <http://github.com/grahambrooks/boost-program-options>

The application is also linked up to a continuous integration server
that triggers a build and test on each commit.

To keep the dependencies as low as possible the tests are written
using the boost unit testing framework.

The tests are somewhat artificial in that they test the command line
options results in an application state and not an observable
behavior but are perfectly valid test cases.

## Basic use case

     app -v --version -username [value] file file

The basic use case offers two variations of switch (-s --switch) a
numeric argument option and a list of files.

== Test:  No parameter behavior

So the first test verifies that we can handle (detect) that no command
line parameters were supplied.

```cpp
#include <boost/test/unit_test.hpp>

#include "command_line_argument_parser.hpp"

BOOST_AUTO_TEST_CASE(recognises_no_supplied_arguments) {
  command_line_argument_parser parser;
  const char *argv[] = {"app"};
  auto args = parser.parse(sizeof(argv)/sizeof(*argv), argv);
  
  BOOST_CHECK(args.no_arguments());
}
```



## Test: Invalid argument

Not everyone is going to get the parameters right so we need to know
when something goes wrong.

```cpp
#include <boost/test/unit_test.hpp>

#include "command_line_argument_parser.hpp"

BOOST_AUTO_TEST_CASE(raise_exception_on_invalid_argument) {
  command_line_argument_parser parser;
  const char *argv[] = {"app", "--silly-argument"};

  BOOST_CHECK_EXCEPTION(parser.parse(sizeof(argv)/sizeof(*argv), argv),
			std::logic_error,
			[] (std::logic_error const& e) -> bool {return strcmp(e.what(), "unrecognised option '--silly-argument'") == 0;});
}
```


## Test: Accepting a switch

Accept switch arguments for binary options

```cpp
#include <boost/test/unit_test.hpp>

#include "command_line_argument_parser.hpp"

BOOST_AUTO_TEST_CASE(recognises_a_switch_argument) {
  command_line_argument_parser parser;
  const char *argv[] = {"app", "--verbose"};
  auto args = parser.parse(sizeof(argv)/sizeof(*argv), argv);
  
  BOOST_CHECK(args.verbose());
}
```


## Test: Accepting a value argument

Accept values on the command line

```cpp
#include <boost/test/unit_test.hpp>

#include "command_line_argument_parser.hpp"

BOOST_AUTO_TEST_CASE(recognises_username_argument) {
  command_line_argument_parser parser;
  const char *argv[] = {"app", "--username","graham"};
  auto args = parser.parse(sizeof(argv)/sizeof(*argv), argv);
  
  BOOST_CHECK_EQUAL("graham", args.username());
}
```


## Test: Filename parameter list

Accept a list of filenames positionally at the end of the command line

```cpp
#include <boost/test/unit_test.hpp>

#include "command_line_argument_parser.hpp"

BOOST_AUTO_TEST_CASE(captures_filenames_from_the_commandline) {
  command_line_argument_parser parser;
  const char *argv[] = {"app", "a", "b", "c"};
  auto args = parser.parse(sizeof(argv)/sizeof(*argv), argv);
  
  auto filename_arguments =  args.filenames();

  BOOST_CHECK_EQUAL(3, filename_arguments.size());
  BOOST_CHECK_EQUAL("a", filename_arguments[0]);
  BOOST_CHECK_EQUAL("b", filename_arguments[1]);
  BOOST_CHECK_EQUAL("c", filename_arguments[2]);
}
```


## Test: Main method to run the tests


```cpp
#define BOOST_TEST_MAIN
#ifndef MAKEFILE_BUILD
// Work around for xcode's default to always link against dynamic libraries
// and the need to static linking for distribution.
#define BOOST_TEST_DYN_LINK
#endif
#define BOOST_TEST_MODULE DupsTests

#include <boost/test/unit_test.hpp>
```


## Application bootstrap

Bootstrapping the app. Main methods need to be as small as possible
and responsible for wiring up the application and passing command line
parameters into the application.

```cpp
#include "application.hpp"

int main(int argc, const char *argv[]) {
  application app;

  return app.run(argc, argv);
}
```


## Main application class

For anything more than this trivial application the application class
would contain the main application algorithm or defer processing to a
collection of other classes.

```cpp
#pragma once

#include "command_line_argument_parser.hpp"

class application {
public:
  int run(int argc, const char* argv[]) {
    command_line_argument_parser parser;

    parser.parse(argc, argv);

    return 0;
  }
};
```


## The command line processing classes

Class responsible for capturing runtime arguments.

```cpp
#pragma once

#include <boost/program_options.hpp>
#include <vector>

using namespace std;

class arguments {
  constexpr static auto username_option_name = "username";
  constexpr static auto username_option = "username,u";
  constexpr static auto verbose_option_name = "verbose";
  constexpr static auto verbose_option = "verbose,v";
  constexpr static auto files_option_name = "input-files";
  
  boost::program_options::variables_map variables;
  friend class command_line_argument_parser;
public:
  arguments(boost::program_options::variables_map variables) : variables(variables) {}

  bool no_arguments() {
    return variables.size() == 0;
  }

  bool verbose() {
    return variables.count(verbose_option_name) > 0;
  }

  string username() {
    return (variables.count(username_option_name) > 0) ? variables[username_option_name].as<string>() : "";
  }
  
  const vector<string>& filenames() {
    return variables[files_option_name].as<vector<string>>();
  }
};

class command_line_argument_parser {
    boost::program_options::options_description desc;
  public:
    command_line_argument_parser() {
      desc.add_options()
	(arguments::verbose_option, "Typical long and short switch argument")
        (arguments::username_option, boost::program_options::value<std::string>(), "username to use")
	(arguments::files_option_name,  boost::program_options::value<vector<string>>(), "input file");
    }

  arguments parse(int argc, const char *argv[]) {
    boost::program_options::variables_map variables;
    
    boost::program_options::positional_options_description p;
    p.add(arguments::files_option_name, -1);

    boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(desc).positional(p).run(), variables);
    
    boost::program_options::notify(variables);

    return arguments(variables);
  }
};
```
