Parse command line arguments by defining a struct
Quick Start
#include <structopt/app.hpp>
struct Options {
// positional argument
// e.g., ./main <file>
std::string config_file;
// optional argument
// e.g., -b "192.168.5.3"
// e.g., --bind_address "192.168.5.3"
//
// options can be delimited with `=` or `:`
// note: single dash (`-`) is enough for short & long option
// e.g., -bind_address=localhost
// e.g., -b:192.168.5.3
//
// the long option can also be provided in kebab case:
// e.g., --bind-address 192.168.5.3
std::optional<std::string> bind_address;
// flag argument
// Use `std::optional<bool>` and provide a default value.
// e.g., -v
// e.g., --verbose
// e.g., -verbose
std::optional<bool> verbose = false;
// directly define and use enum classes to limit user choice
// e.g., --log-level debug
// e.g., -l error
enum class LogLevel { debug, info, warn, error, critical };
std::optional<LogLevel> log_level = LogLevel::info;
// pair argument
// e.g., -u <first> <second>
// e.g., --user <first> <second>
std::optional<std::pair<std::string, std::string>> user;
// use containers like std::vector
// to collect "remaining arguments" into a list
std::vector<std::string> files;
};
STRUCTOPT(Options, config_file, bind_address, verbose, log_level, user, files);Create a structopt::app and parse the command line arguments into the Options struct:
int main(int argc, char *argv[]) {
try {
// Line of code that does all the work:
auto options = structopt::app("my_app").parse<Options>(argc, argv);
// Print out parsed arguments:
// std::cout << "config_file = " << options.config_file << "\n";
// std::cout << "bind_address = " << options.bind_address.value_or("not provided") << "\n";
// std::cout << "verbose = " << std::boolalpha << options.verbose.value() << "\n";
// ...
} catch (structopt::exception& e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}Now let's pass some arguments to this program:
foo@bar:~$ ./main config.csv file5.csv file6.json
config_file = config.csv
bind_address = not provided
verbose = false
log_level = 1
user = not provided
files = { file5.csv file6.json }
foo@bar:~$ ./main config.csv --bind-address localhost:9000 -v -log-level error file1.txt file2.txt
config_file = config.csv
bind_address = localhost:9000
verbose = true
log_level = 3
user = not provided
files = { file1.txt file2.txt }
foo@bar:~$ ./main config_2.csv --bind-address 192.168.7.3 -log-level debug file1.txt file3.txt file4.txt --user "John Doe" "john.doe@foo.com"
config_file = config_2.csv
bind_address = 192.168.7.3
verbose = false
log_level = 0
user = John Doe<john.doe@foo.com>
files = { file1.txt file3.txt file4.txt }Table of Contents
- Getting Started
- Building Samples and Tests
- Compiler Compatibility
- Generating Single Header
- Contributing
- License
Getting Started
structopt is a header-only library. Just add include/ to your include_directories and you should be good to go. A single header file version is also available in single_include/.
Positional Arguments
Here's an example of two positional arguments: input_file and output_file. input_file is expected to be the first argument and output_file is expected to be the second argument
#include <structopt/app.hpp>
struct FileOptions {
// Positional arguments
// ./main <input_file> <output_file>
std::string input_file;
std::string output_file;
};
STRUCTOPT(FileOptions, input_file, output_file);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<FileOptions>(argc, argv);
// Print parsed arguments:
std::cout << "\nInput file : " << options.input_file << "\n";
std::cout << "Output file : " << options.output_file << "\n";
} catch (structopt::exception& e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main foo.txt bar.csv
Input file : foo.txt
Output file : bar.csv
foo@bar:~$ ./main foo.csv
Error: expected value for positional argument `output_file`.
USAGE: ./my_app input_file output_file
ARGS:
input_file
output_fileOptional Arguments
Now, let's look at optional arguments. To configure an optional argument, use std::optional in the options struct like below.
#include <structopt/app.hpp>
struct GccOptions {
// language standard
// e.g., -std=c++17
// e.g., --std c++20
std::optional<std::string> std = "c++11";
// verbosity enabled with `-v` or `--verbose`
// or `-verbose`
std::optional<bool> verbose = false;
// enable all warnings with `-Wall`
std::optional<bool> Wall = false;
// produce only the compiled code
// e.g., gcc -C main.c
std::optional<bool> Compile = false;
// produce output with `-o <exec_name>`
std::optional<std::string> output = "a.out";
std::string input_file;
};
STRUCTOPT(GccOptions, std, verbose, Wall, Compile, output, input_file);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("gcc").parse<GccOptions>(argc, argv);
// Print parsed arguments
std::cout << "std : " << options.std.value() << "\n";
std::cout << "verbose : " << std::boolalpha << options.verbose.value() << "\n";
std::cout << "Wall : " << std::boolalpha << options.Wall.value() << "\n";
std::cout << "Compile : " << std::boolalpha << options.Compile.value() << "\n";
std::cout << "Output : " << options.output.value() << "\n";
std::cout << "Input file : " << options.input_file << "\n";
} catch (structopt::exception &e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}NOTE structopt supports two option delimiters, = and : for optional arguments. This is meaningful and commonly used in single-valued optional arguments, e.g., --std=c++17.
foo@bar:~$ ./main -C main.cpp
std : c++11
verbose : false
Wall : false
Compile : true
Output : a.out
Input file : main.cpp
foo@bar:~$ ./main -std=c++17 -o main main.cpp
std : c++17
verbose : false
Wall : false
Compile : false
Output : main
Input file : main.cpp
foo@bar:~$ ./main main.cpp -v -std:c++14 --output:main -Wall
std : c++14
verbose : true
Wall : true
Compile : false
Output : main
Input file : main.cppNOTE In summary, for a field in your struct named bind_address, the following are all legal ways to provide a value:
- Short form:
-b <value>
- Long form:
--bind_address <value>-bind_address <value>
- Kebab case:
--bind-address <value>-bind-address <value>
- Equal (
'=') option delimiter-b=<value>--bind_address=<value>-bind_address=<value>--bind-address=<value>-bind-address=<value>
- Colon
':'option delimiter-b:<value>--bind_address:<value>-bind_address:<value>--bind-address:<value>-bind-address:<value>
Double dash (--) Argument
A double dash (--) is used in most bash built-in commands and many other commands to signify the end of command options, after which only positional parameters are accepted.
Example use: lets say you want to grep a file for the string -v - normally -v will be considered the option to reverse the matching meaning (only show lines that do not match), but with -- you can grep for string -v like this:
#include <structopt/app.hpp>
struct GrepOptions {
// reverse the matching
// enable with `-v`
std::optional<bool> v = false;
// positional arguments
std::string search;
std::string pathspec;
};
STRUCTOPT(GrepOptions, v, search, pathspec);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<GrepOptions>(argc, argv);
if (options.v == true) {
std::cout << "`-v` provided - Matching is now reversed\n";
}
std::cout << "Search : " << options.search << "\n";
std::cout << "Pathspec : " << options.pathspec << "\n";
}
catch (structopt::exception& e) {
std::cout << e.what();
std::cout << e.help();
}
}foo@bar:~$ ./main -v foo bar.txt
`-v` provided - Matching is now reversed
Search : foo
Pathspec : bar.txt
foo@bar:~$ ./main -- -v bar.txt
Search : -v
Pathspec : bar.txtFlag Arguments
Flag arguments are std::optional<bool> with a default value.
NOTE The default value here is important. It is not a flag if a default value isn't provided. It will simply be an optional argument.
NOTE If --verbose is a flag argument with a default value of false, then providing the argument will set it to true. If --verbose does not have a default value, then structopt will expect the user to provide a value, e.g., --verbose true.
#include <structopt/app.hpp>
struct Options {
// verbosity flag
// -v, --verbose
// remember to provide a default value
std::optional<bool> verbose = false;
};
STRUCTOPT(Options, verbose);
int main(int argc, char *argv[]) {
auto options = structopt::app("my_app").parse<Options>(argc, argv);
if (options.verbose == true) {
std::cout << "Verbosity enabled\n";
}
}foo@bar:~$ ./main
foo@bar:~$ ./main -v
Verbosity enabled
foo@bar:~$ ./main --verbose
Verbosity enabledEnum Class Arguments
Thanks to magic_enum, structopt supports enum classes. You can use an enum classes to ask the user to provide a value given a choice of values, restricting the possible set of allowed input arguments.
#include <structopt/app.hpp>
struct StyleOptions {
enum class Color {red, green, blue};
// e.g., `--color red`
std::optional<Color> color = Color::red;
};
STRUCTOPT(StyleOptions, color);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<StyleOptions>(argc, argv);
// Use parsed argument `options.color`
if (options.color == StyleOptions::Color::red) {
std::cout << "#ff0000\n";
}
else if (options.color == StyleOptions::Color::blue) {
std::cout << "#0000ff\n";
}
else if (options.color == StyleOptions::Color::green) {
std::cout << "#00ff00\n";
}
} catch (structopt::exception& e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main --color red
#ff0000
foo@bar:~$ ./main -c blue
#0000ff
foo@bar:~$ ./main --color green
#00ff00
foo@bar:~$ ./main -c black
Error: unexpected input `black` provided for enum argument `color`. Allowed values are {red, green, blue}
USAGE: ./my_app [OPTIONS]
OPTIONS:
-c, --color <color>Tuple Arguments
Now that we've looked at enum class support, let's build a simple calculator. In this sample, we will use an std::tuple to pack all the arguments to the calculator:
#include <structopt/app.hpp>
struct CalculatorOptions {
// types of operations supported
enum class operation { add, subtract, multiply, divide };
// single tuple positional argument
std::tuple<operation, int, int> input;
};
STRUCTOPT(CalculatorOptions, input);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<CalculatorOptions>(argc, argv);
auto op = std::get<0>(options.input);
auto lhs = std::get<1>(options.input);
auto rhs = std::get<2>(options.input);
switch(op)
{
case CalculatorOptions::operation::add:
std::cout << lhs + rhs << "\n";
break;
case CalculatorOptions::operation::subtract:
std::cout << lhs - rhs << "\n";
break;
case CalculatorOptions::operation::multiply:
std::cout << lhs * rhs << "\n";
break;
case CalculatorOptions::operation::divide:
std::cout << lhs / rhs << "\n";
break;
}
}
catch (structopt::exception& e) {
std::cout << e.what();
std::cout << e.help();
}
}foo@bar:~$ ./main add 1 2
3
foo@bar:~$ ./main subtract 5 9
-4
foo@bar:~$ ./main multiply 16 5
80
foo@bar:~$ ./main divide 1331 11
121
foo@bar:~$ ./main add 5
Error: failed to correctly parse tuple `input`. Expected 3 arguments, 2 provided.
USAGE: my_app input
ARGS:
inputVector Arguments
structopt supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
$ compiler file1 file2 file3Do this by using an std::vector<T> (or other STL containers with .push_back(), e.g, std::deque or std::list).
NOTE Vector arguments have a cardinality of 0..*, i.e., zero or more arguments. Unlike array types, you can provide zero arguments to a vector and structopt will (try to) not complain.
#include <structopt/app.hpp>
struct CompilerOptions {
// Language standard
// e.g., --std c++17
std::optional<std::string> std;
// remaining arguments
// e.g., ./compiler file1 file2 file3
std::vector<std::string> files{};
};
STRUCTOPT(CompilerOptions, std, files);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<CompilerOptions>(argc, argv);
std::cout << "Standard : " << options.std.value_or("not provided") << "\n";
std::cout << "Files : { ";
std::copy(options.files.begin(), options.files.end(),
std::ostream_iterator<std::string>(std::cout, " "));
std::cout << "}" << std::endl;
} catch (structopt::exception &e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}NOTE Notice below that the act of gathering remaining arguments is arrested as soon as an optional argument is detected. See the output of ./main file1.cpp file2.cpp --std c++17 below. Notice that --std=c++17 is not part of the vector. This is because --std is a valid optional argument.
foo@bar:~$ ./main
Standard : not provided
Files : { }
foo@bar:~$ ./main file1.cpp file2.cpp
Standard : not provided
Files : { file1.cpp file2.cpp }
foo@bar:~$ ./main file1.cpp file2.cpp --std=c++17
Standard : c++17
Files : { file1.cpp file2.cpp }
foo@bar:~$ ./main --std:c++20 file1.cpp file2.cpp
Standard : c++20
Files : { file1.cpp file2.cpp }Compound Arguments
Compound arguments are optional arguments that are combined and provided as a single argument. Example: ps -aux
#include <structopt/app.hpp>
struct Options {
// Flag arguments
std::optional<bool> a = false;
std::optional<bool> b = false;
// Optional argument
// e.g., -c 1.1 2.2
std::optional<std::array<float, 2>> c = {};
};
STRUCTOPT(Options, a, b, c);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<Options>(argc, argv);
// Print parsed arguments:
std::cout << std::boolalpha << "a = " << options.a.value()
<< ", b = " << options.b.value() << "\n";
if (options.c.has_value()) {
std::cout << "c = [" << options.c.value()[0] << ", " << options.c.value()[1]
<< "]\n";
}
} catch (structopt::exception &e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main -ac 3.14 2.718
a = true, b = false
c = [3.14, 2.718]
foo@bar:~$ ./main -ba
a = true, b = true
foo@bar:~$ ./main -c 1.5 3.0 -ab
a = true, b = true
c = [1.5, 3]Parsing Numbers
Integer Literals
structopt supports parsing integer literals including hexadecimal, octal, and binary notation.
#include <structopt/app.hpp>
struct IntegerLiterals {
std::vector<int> numbers;
};
STRUCTOPT(IntegerLiterals, numbers);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<IntegerLiterals>(argc, argv);
for (auto &n : options.numbers)
std::cout << n << "\n";
} catch (structopt::exception &e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main 1 0x5B 071 0b0101 -35 +98
1
91
57
5
-35
98Floating point Literals
As for floating point numbers, structopt supports parsing scientific notation (e/E-notation):
#include <structopt/app.hpp>
struct FloatLiterals {
std::vector<float> numbers;
};
STRUCTOPT(FloatLiterals, numbers);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<FloatLiterals>(argc, argv);
for (auto &n : options.numbers)
std::cout << n << "\n";
} catch (structopt::exception &e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main -3.15 +2.717 2E-4 0.1e2 .5 -.3 +5.999
-3.15
2.717
0.0002
10
0.5
-0.3
5.999Nested Structures
With structopt, you can define sub-commands, e.g., git init args or git config [flags] args using nested structures.
- Simply create a nested structure that inherits from
structopt::sub_command - You can use
<nested_struct_object>.has_value()to check if it has been invoked.
The following program support two sub-commands: config and init:
#include <structopt/app.hpp>
struct Git {
// Subcommand: git config
struct Config : structopt::sub_command {
// flag argument `--global`
std::optional<bool> global = false;
// key-value pair, e.g., `user.name "John Doe"`
std::array<std::string, 2> name_value_pair{};
};
Config config;
// Subcommand: git init
struct Init : structopt::sub_command {
// required argument
// repository name
std::string name;
};
Init init;
};
STRUCTOPT(Git::Config, global, name_value_pair);
STRUCTOPT(Git::Init, name);
STRUCTOPT(Git, config, init);
int main(int argc, char *argv[]) {
try {
auto options = structopt::app("my_app").parse<Git>(argc, argv);
if (options.config.has_value()) {
// config was invoked
std::cout << "You invoked `git config`:\n";
std::cout << "Global : " << std::boolalpha << options.config.global.value() << "\n";
std::cout << "Input : (" << options.config.name_value_pair[0] << ", " << options.config.name_value_pair[1] << ")\n";
}
else if (options.init.has_value()) {
// init was invoked
std::cout << "You invoked `git init`:\n";
std::cout << "Repository name : " << options.init.name << "\n";
}
} catch (structopt::exception& e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main config user.email "john.doe@foo.com"
You invoked `git config`:
Global : false
Input : (user.email, john.doe@foo.com)
foo@bar:~$ ./main config user.name "John Doe" --global
You invoked `git config`:
Global : true
Input : (user.name, John Doe)
foo@bar:~$ ./main init my_repo
You invoked `git init`:
Repository name : my_repo
foo@bar:~$ ./main -h
USAGE: my_app [OPTIONS] [SUBCOMMANDS]
OPTIONS:
-h, --help <help>
-v, --version <version>
SUBCOMMANDS:
config
init
foo@bar:~$ ./main config -h
USAGE: config [FLAGS] [OPTIONS] name_value_pair
FLAGS:
-g, --global
OPTIONS:
-h, --help <help>
-v, --version <version>
ARGS:
name_value_pair
foo@bar:~$ ./main init -h
USAGE: init [OPTIONS] name
OPTIONS:
-h, --help <help>
-v, --version <version>
ARGS:
nameNOTE Notice in the above stdout that the -h help option supports printing help both at the top-level struct and at the sub-command level.
NOTE structopt does not allow to invoke multiple sub-commands. If one has already been invoked, you will see the following error:
foo@bar:~$ ./main config user.name "John Doe" init my_repo
Error: failed to invoke sub-command `init` because a different sub-command, `config`, has already been invoked.Sub-Commands, Vector Arguments, and Delimited Positional Arguments
Here's a second example for nested structures with vector arguments and the double dash (--) delimiter
#include <structopt/app.hpp>
struct CommandOptions {
struct Sed : structopt::sub_command {
// --trace
std::optional<bool> trace = false;
// remaining args
std::vector<std::string> args;
// pattern
std::string pattern;
// file
std::string file;
};
Sed sed;
};
STRUCTOPT(CommandOptions::Sed, trace, args, pattern, file);
STRUCTOPT(CommandOptions, sed);
int main(int argc, char *argv[]) {
auto app = structopt::app("my_app");
try {
auto options = app.parse<CommandOptions>(argc, argv);
if (options.sed.has_value()) {
// sed has been invoked
if (options.sed.trace == true) {
std::cout << "Trace enabled!\n";
}
std::cout << "Args : ";
for (auto& a : options.sed.args) std::cout << a << " ";
std::cout << "\n";
std::cout << "Pattern : " << options.sed.pattern << "\n";
std::cout << "File : " << options.sed.file << "\n";
}
else {
std::cout << app.help();
}
} catch (structopt::exception &e) {
std::cout << e.what() << "\n";
std::cout << e.help();
}
}foo@bar:~$ ./main
USAGE: my_app [OPTIONS] [SUBCOMMANDS]
OPTIONS:
-h, --help <help>
-v, --version <version>
SUBCOMMANDS:
sed
foo@bar:~$ ./main sed --trace X=1 Y=2 Z=3 -- 's/foo/bar/g' foo.txt
Trace enabled!
Args : X=1 Y=2 Z=3
Pattern : s/foo/bar/g
File : foo.txtPrinting Help
structopt will insert two optional arguments for the user: help and version.
- Using
-hor--helpwill print the help message and exit. - Using
-vor--versionwill print the program version and exit.
#include <structopt/app.hpp>
struct Options {
// positional arguments
std::string input_file;
std::string output_file;
// optional arguments
std::optional<std::string> bind_address;
// remaining arguments
std::vector<std::string> files;
};
STRUCTOPT(Options, input_file, output_file, bind_address, files);
int main(int argc, char *argv[]) {
auto options = structopt::app("my_app", "1.0.3").parse<Options>(argc, argv);
}foo@bar:~$ ./main -h
USAGE: my_app [OPTIONS] input_file output_file files
OPTIONS:
-b, --bind-address <bind_address>
-h, --help <help>
-v, --version <version>
ARGS:
input_file
output_file
files
foo@bar:~$ ./main -v
1.0.3NOTE Admittedly, the above help message doesn't look great; none of the arguments have a description - something that is configurable in other argument parsers. structopt does its best to infer details about arguments from the user-defined struct including argument name, data type, and argument type. Unforunately, structopt (for now) does not provide any API to the user to configure (e.g., by providing a map) documentation for each of the fields in the struct.
Building Samples and Tests
git clone https://github.com/p-ranav/structopt
cd structopt
mkdir build && cd build
cmake -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON ..
makeCompiler Compatibility
- Clang/LLVM >= 5
- MSVC++ >= 14.11 / Visual Studio >= 2017
- Xcode >= 10
- GCC >= 9
Generating Single Header
python3 utils/amalgamate/amalgamate.py -c single_include.json -s .Contributing
Contributions are welcome, have a look at the CONTRIBUTING.md document for more information.
License
The project is available under the MIT license.

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

