CLI11 started years ago as a set of tools built on Boost Program Options (PO), and has since matured into the powerful, easy-to-use stand-alone library it is available today. If you would like to see the original inspiration for CLI11, look at Program.hpp in CLI11 0.1. The rest of the post will focus on a comparison between making a CLI app in the two libraries. I am going to assume that you are preparing fairly basic but non-trivial programs in the following comparison.
TL;DR: CLI11 is more concise, and provides more control with better defaults in many cases, but was inspired by Boost PO.
Build system
CLI11
CLI11 only requires a single header file and C++11. That’s it. You can use the multi-file version as well, and it comes with a CMake file, but you don’t have to use either if you don’t want to. Nothing special is required on macOS, Windows, or Linux.
Boost PO
Boost PO requires that you use Boost, and it is old enough that it requires linking to a compiled library (most new Boost libs are header only). If you are using C++11 or better, the chances that you’ll be using Boost are a bit lower than in the past, but there’s still a good chance you’ll be requiring it. However, linking to one of the few Boost libraries is a bit irritating for something as simple as command line parsing.
Initializing
CLI11
CLI11 expects you to create an App
for your program. You can optionally add a
description when you make the app.
#include "CLI11.hpp" // Single file version
...
CLI::App app{"Description"};
Boost PO
Using Boost PO requires that you manage several objects. Usage will vary depending on what features you want. You’ll probably want something like this:
#include <boost/program_options.hpp>
namespace po = boost::program_options;
...
po::options_description desc{"Description"};
po::positional_options_description p;
po::variables_map vm;
Adding a flag
CLI11
There are two ways. The first is similar to PO, and was inspired by it:
auto flag = app.add_flag("-f,--flag",
"A flag");
The preferred way would be to add a variable (bool
or int
) to bind to:
bool flag;
app.add_flag("-f,--flag", flag, "A flag");
You can have as many long or short names for each flag as you want.
Boost PO
You have to use an odd callable syntax to add options to Boost PO. However, you can keep adding one after the other, which might help keep the options close and saves a few characters. You also have to manually add a help flag.
desc.add_options()
("help,h", "produce help message")
("flag,f", "A flag")
;
Adding an option
CLI11
CLI11 only provides bound variables, since the values are pure values you can use with no run time lookup or translation penalty.
int value = 2;
double val;
app.add_option("--option", value,
"Set an option", true);
app.add_option("positional", val,
"A positional");
The final true
here tells CLI11 that it can print the default value in help;
if you are not worried about that, you don’t need it. You can make an option as
required by adding ->required()
to this statement. Positional arguments are
marked by having a name with no dashes; a purely positional option is possible!
Boost PO
There are two ways to add options in Boost PO. The first way does not require an option to bind to, and is not mimicked in CLI11:
desc.add_options()
("something",
po::value<int>(),
"Non-bound option")
;
The second way, where you provide a variable to bind to, is what inspired the
method used in CLI11 (and in the old Program.hpp
):
int value = 2;
double val;
desc.add_options()
("option",
po::value<int>(&value)
->default_value(value),
"Set an option")
("positional",
po::value<double>(&val),
"A positional")
;
p.add("positional", -1);
You can also mark an option as required by chaining settings on the po::value
(which does not provide good factorizability or readability, IMO).
Parsing
CLI11
CLI11 shines here, since it was designed to work correctly for most uses out of the box, and then provide ways to change defaults.
CLI11_PARSE(app, argc, argv);
If you feel like a macro is cheating, this is all you need to implement this without any macros (and is what the above macro expands to):
try {
app.parse(argc, argv);
} catch (const CLI::ParseError& e) {
return app.exit(e);
}
Boost PO
Here’s where Boost PO gets tricky. To correctly exit in case of a parse error or help message, there are quite a few steps you have to do in every program. For non-expert users, often these steps get skipped and the program can hard-crash or fail to print help if there are required options.
try {
po::store(
po::command_line_parser(argc, argv)
.options(desc).positional(p).run(), vm);
if(vm.count("help")){
std::cout << desc;
exit(0);
}
po::notify(vm);
} catch(const po::error& e) {
std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
std::cerr << desc << std::endl;
exit(1);
}
Accessing values
CLI11
Options and flags are bound directly to the results. You can also look up the count using Boost PO inspired syntax, either on the pointer to the option if you saved it or on the master app:
int iflag = flag->count();
int iflag = app.count("flag");
Boost PO
While regular options can be directly bound, flags and non-bound regular options must be looked up and converted manually, at a runtime cost.
int flag = vm.count("flag");
something = vm["something"].as<int>()
Subcommands
CLI11
CLI11 naturally supports subcommands, which are full App
instances, and can be
nested infinitely, using:
add_subcommand("name", "description");
Boost PO
Not directly supported by Boost PO. There is a project called Oberon to add these, but it doesn’t seem to be maintained. You can manually do it yourself by collecting the extra unrecognized options and reparsing them, as shown in this gist.
Other features
Supported by both libraries, though usually much more concisely in CLI11:
- Groups
- Required options
- INI files
- Environment variable input
- Callback lambda functions
- Hidden options
- Extra argument polices (more in CLI11 though)
CLI11 only:
- Subcommands (already mentioned)
- Requires/excludes options
- Case insensitive options
- Sets
- Vectors of options
- Multi-option policies
- Custom error printer function setting
- Flags and subcommands in INI files
- INI file generation
- Option validators (not the same as a
.validate
function in Boost PO) - Option text transformers
Boost PO only:
- Response files
- Winmain splitter
- Unicode support (in some parsers)
boost::optional
support (planned in CLI11)