Command-Line Arguments with CLI11
CLI11 is a header-only C++ library for defining command-line options, parsing
arguments, converting values into C++ types, validating input, and generating
help output. This minimal C++17 example adds a --name option to a greeting
program.
Install on Ubuntu 24.04
Ubuntu 24.04 provides CLI11 2.4.1. Confirm the installed package with:
Because CLI11 is header-only, the package primarily provides headers and CMake metadata rather than a runtime library.
Minimal example
Create the command-line application and its description:
Define a normal C++ variable and bind an option to it:
The option has two equivalent names:
-nis the short form;--nameis the readable long form.
If the user supplies the option, CLI11 stores its value in name. Otherwise,
the original value "world" remains unchanged.
CLI11_PARSE parses the arguments and handles ordinary parsing failures, such
as an unknown option or a missing value. It also enables automatically generated
--help output.
Define options before parsing
Register every option and flag on app before calling CLI11_PARSE. Read
the bound variables only after parsing succeeds.
CMake configuration
find_package locates the Ubuntu development package. Link the executable to
the imported header-only target:
The target provides CLI11's include directory and usage requirements.
Build and run
Download main.cpp and CMakeLists.txt into one directory, then run:
Run without an option to use the default value:
Use either option name to provide another value:
Display the generated help page:
CLI11 uses the bound variable's C++ type to convert input. For example, binding
an option to an int asks CLI11 to parse an integer and report invalid text to
the user.
Demo:
Common CLI11 features
The following examples focus on one feature at a time. Add the options before
CLI11_PARSE, then use their bound variables after parsing succeeds.
1. Flags
A flag represents an on/off choice and does not consume a value:
Enable it by including its name:
The bound Boolean remains false when the flag is absent and becomes true
when it is present.
2. Required options
Call required() when the application cannot run without an option:
CLI11 prints an error and usage information when --config is missing.
Required means the user must provide it
Giving the bound variable a C++ default value does not satisfy
required(). Use a required option only when silently choosing a default
would be incorrect.
3. Default-value display
capture_default_str() records the variable's current value and displays it in
the generated help:
The help output then includes the default:
Call capture_default_str() after assigning the intended C++ default.
4. Validators
Validators reject values that have the correct type but are not acceptable for the application:
This fails because 70000 is outside the permitted range. Useful built-in
validators include:
Type conversion happens as well: an option bound to int rejects text that is
not an integer.
5. Positional arguments
An option name without - or -- creates a positional argument:
The value is written without an option name:
Positional arguments are convenient for the primary input or output. Named options are usually clearer for optional settings.
6. Vectors and repeated options
Bind an option to a vector when it may receive several values:
The user can provide several values together or repeat the option:
After parsing, inputs contains every supplied filename. Here, expected(1,
-1) means at least one value and no fixed upper limit.
7. Environment variables
envname() supplies an option from an environment variable when it was not
provided on the command line:
The effective precedence is:
Therefore, ./app --log-level warn overrides APP_LOG_LEVEL=debug.
8. Dependencies and exclusions
needs() requires another option when the first option is used:
--token secret now requires --username Alice as well.
excludes() prevents incompatible options from appearing together:
CLI11 rejects ./app --json --yaml. One exclusion declaration is sufficient
for this pair.
9. Subcommands
Subcommands divide one executable into separate operations, similar to
git clone, git status, and git log. Each subcommand can have its own
description, options, positional arguments, and help page.
Usage follows the structure of the program:
app.require_subcommand(1) requires exactly one subcommand. Remove it when the
program has useful behavior without a subcommand.
The pointer returned by add_subcommand behaves as a Boolean after parsing:
Options added to start are local to start; they normally appear after the
subcommand name. Options added directly to the parent app are global. Help is
available at both levels:
The parent help lists available subcommands, while the second command shows the
options specific to start. Subcommands may also have aliases and nested
subcommands, but one level is easier to understand and is sufficient for most
small tools.
Keep dispatch after parsing
For a simple application, parse once and dispatch with if (*subcommand).
Callbacks are useful later, but explicit dispatch makes control flow and
shared initialization easier to see.