Need help with argparse?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

p-ranav
871 Stars 87 Forks MIT License 398 Commits 21 Opened issues

Description

Argument Parser for Modern C++

Services available

!
?

Need anything else?

Contributors list

argparse

travis license version

Highlights

  • Single header file
  • Requires C++17
  • MIT License

Quick Start

Simply include argparse.hpp and you're good to go.

#include 

To start parsing command-line arguments, create an

ArgumentParser
.
argparse::ArgumentParser program("program_name");

NOTE: There is an optional second argument to the

ArgumentParser
which is the program version. Example:
argparse::ArgumentParser program("libfoo", "1.9.0");

To add a new argument, simply call

.add_argument(...)
. You can provide a variadic list of argument names that you want to group together, e.g.,
-v
and
--verbose
program.add_argument("foo");
program.add_argument("-v", "--verbose"); // parameter packing

Argparse supports a variety of argument types including positional, optional, and compound arguments. Below you can see how to configure each of these types:

Positional Arguments

Here's an example of a positional argument:

#include 

int main(int argc, char *argv[]) { argparse::ArgumentParser program("program_name");

program.add_argument("square") .help("display the square of a given integer") .scan();

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto input = program.get("square"); std::cout << (input * input) << std::endl;

return 0; }

And running the code:

$ ./main 15
225

Here's what's happening:

  • The
    add_argument()
    method is used to specify which command-line options the program is willing to accept. In this case, I’ve named it square so that it’s in line with its function.
  • Command-line arguments are strings. To square the argument and print the result, we need to convert this argument to a number. In order to do this, we use the
    .scan
    method to convert user input into an integer.
  • We can get the value stored by the parser for a given argument using
    parser.get(key)
    method.

Optional Arguments

Now, let's look at optional arguments. Optional arguments start with

-
or
--
, e.g.,
--verbose
or
-a
. Optional arguments can be placed anywhere in the input sequence.
argparse::ArgumentParser program("test");

program.add_argument("--verbose") .help("increase output verbosity") .default_value(false) .implicit_value(true);

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

if (program["--verbose"] == true) { std::cout << "Verbosity enabled" << std::endl; }

$ ./main --verbose
Verbosity enabled

Here's what's happening: * The program is written so as to display something when --verbose is specified and display nothing when not. * Since the argument is actually optional, no error is thrown when running the program without

--verbose
. Note that by using
.default_value(false)
, if the optional argument isn’t used, it's value is automatically set to false. * By using
.implicit_value(true)
, the user specifies that this option is more of a flag than something that requires a value. When the user provides the --verbose option, it's value is set to true.

Requiring optional arguments

There are scenarios where you would like to make an optional argument required. As discussed above, optional arguments either begin with

-
or
--
. You can make these types of arguments required like so:
program.add_argument("-o", "--output")
  .required()
  .help("specify the output file.");

If the user does not provide a value for this parameter, an exception is thrown.

Alternatively, you could provide a default value like so:

program.add_argument("-o", "--output")
  .default_value(std::string("-"))
  .required()
  .help("specify the output file.");

Accessing optional arguments without default values

If you require an optional argument to be present but have no good default value for it, you can combine testing and accessing the argument as following:

if (auto fn = program.present("-o")) {
    do_something_with(*fn);
}

Similar to

get
, the
present
method also accepts a template argument. But rather than returning
T
,
parser.present(key)
returns
std::optional
, so that when the user does not provide a value to this parameter, the return value compares equal to
std::nullopt
.

Deciding if the value was given by the user

If you want to know whether the user supplied a value for an argument that has a

.default_value
, check whether the argument
.is_used()
.
program.add_argument("--color")
  .default_value("orange")
  .help("specify the cat's fur color");

try { program.parse_args(argc, argv); // Example: ./main --color orange } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto color = program.get<:string>("--color"); // "orange" auto explicit_color = program.is_used("--color"); // true, user provided orange </:string>

Joining values of repeated optional arguments

You may want to allow an optional argument to be repeated and gather all values in one place.

program.add_argument("--color")
  .default_value<:vector>>({ "orange" })
  .append()
  .help("specify the cat's fur color");

try { program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto colors = program.get<:vector>>("--color"); // {"red", "green", "blue"} </:vector></:vector>

Notice that

.default_value
is given an explicit template parameter to match the type you want to
.get
.

Repeating an argument to increase a value

A common pattern is to repeat an argument to indicate a greater value.

int verbosity = 0;
program.add_argument("-V", "--verbose")
  .action([&](const auto &) { ++verbosity; })
  .append()
  .default_value(false)
  .implicit_value(true)
  .nargs(0);

program.parse_args(argc, argv); // Example: ./main -VVVV

std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4

Negative Numbers

Optional arguments start with

-
. Can
argparse
handle negative numbers? The answer is yes!
argparse::ArgumentParser program;

program.add_argument("integer") .help("Input number") .scan();

program.add_argument("floats") .help("Vector of floats") .nargs(4) .scan();

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

// Some code to print arguments

$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3
integer : -5
floats  : -1.1 -3.1415 -310 -4513.29

As you can see here,

argparse
supports negative integers, negative floats and scientific notation.

Combining Positional and Optional Arguments

argparse::ArgumentParser program("main");

program.add_argument("square") .help("display the square of a given number") .scan();

program.add_argument("--verbose") .default_value(false) .implicit_value(true);

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

int input = program.get("square");

if (program["--verbose"] == true) { std::cout << "The square of " << input << " is " << (input * input) << std::endl; } else { std::cout << (input * input) << std::endl; }

$ ./main 4
16

$ ./main 4 --verbose The square of 4 is 16

$ ./main --verbose 4 The square of 4 is 16

Printing Help

std::cout << program
prints a help message, including the program usage and information about the arguments registered with the
ArgumentParser
. For the previous example, here's the default help message:
$ ./main --help
Usage: main [options] square

Positional arguments: square display the square of a given number

Optional arguments: -h --help shows help message and exits [default: false] -v --version prints version information and exits [default: false] --verbose [default: false]

You may also get the help message in string via

program.help().str()
.

List of Arguments

ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The

.nargs
associates a different number of command-line arguments with a single action. When using
nargs(N)
, N arguments from the command line will be gathered together into a list.
argparse::ArgumentParser program("main");

program.add_argument("--input_files") .help("The list of input files") .nargs(2);

try { program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto files = program.get<:vector>>("--input_files"); // {"config.yml", "System.xml"} </:vector>

ArgumentParser.get()
has specializations for
std::vector
and
std::list
. So, the following variant,
.get<:list>
, will also work.
auto files = program.get<:list>>("--input_files");  // {"config.yml", "System.xml"}

Using

.scan
, one can quickly build a list of desired value types from command line arguments. Here's an example:
argparse::ArgumentParser program("main");

program.add_argument("--query_point") .help("3D query point") .nargs(3) .default_value(std::vector{0.0, 0.0, 0.0}) .scan();

try { program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2 } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto query_point = program.get<:vector>>("--query_point"); // {3.5, 4.7, 9.2} </:vector>

Compound Arguments

Compound arguments are optional arguments that are combined and provided as a single argument. Example:

ps -aux
argparse::ArgumentParser program("test");

program.add_argument("-a") .default_value(false) .implicit_value(true);

program.add_argument("-b") .default_value(false) .implicit_value(true);

program.add_argument("-c") .nargs(2) .default_value(std::vector{0.0f, 0.0f}) .scan();

try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get<:vector>>("-c"); // {1.95, 2.47}

/// Some code that prints parsed arguments </:vector>

$ ./main -ac 3.14 2.718
a = true
b = false
c = {3.14, 2.718}

$ ./main -cb a = false b = true c = {0.0, 0.0}

Here's what's happening: * We have three optional arguments

-a
,
-b
and
-c
. *
-a
and
-b
are toggle arguments. *
-c
requires 2 floating point numbers from the command-line. * argparse can handle compound arguments, e.g.,
-abc
or
-bac
or
-cab
. This only works with short single-character argument names. -
-a
and
-b
become true. - argv is further parsed to identify the inputs mapped to
-c
. - If argparse cannot find any arguments to map to c, then c defaults to {0.0, 0.0} as defined by
.default_value

Converting to Numeric Types

For inputs, users can express a primitive type for the value.

The

.scan
method attempts to convert the incoming
std::string
to
T
following the
Shape
conversion specifier. An
std::invalid_argument
or
std::range_error
exception is thrown for errors.
program.add_argument("-x")
       .scan();

program.add_argument("scale") .scan();

Shape
specifies what the input "looks like", and the type template argument specifies the return value of the predefined action. Acceptable types are floating point (i.e float, double, long double) and integral (i.e. signed char, short, int, long, long long).

The grammar follows

std::from_chars
, but does not exactly duplicate it. For example, hexadecimal numbers may begin with
0x
or
0X
and numbers with a leading zero may be handled as octal values.

| Shape | interpretation | | :--------: | ----------------------------------------- | | 'a' or 'A' | hexadecimal floating point | | 'e' or 'E' | scientific notation (floating point) | | 'f' or 'F' | fixed notation (floating point) | | 'g' or 'G' | general form (either fixed or scientific) | | | | | 'd' | decimal | | 'i' |

std::from_chars
grammar with base == 0 | | 'o' | octal (unsigned) | | 'u' | decimal (unsigned) | | 'x' or 'X' | hexadecimal (unsigned) |

Default Arguments

argparse
provides predefined arguments and actions for
-h
/
--help
and
-v
/
--version
. These default actions exit the program after displaying a help or version message, respectively. These defaults arguments can be disabled during
ArgumentParser
creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.)
argparse::ArgumentParser program("test", "1.0", default_arguments::none);

program.add_argument("-h", "--help") .action([=](const std::string& s) { std::cout << help().str(); }) .default_value(false) .help("shows help message") .implicit_value(true) .nargs(0);

The above code snippet outputs a help message and continues to run. It does not support a

--version
argument.

The default is

default_arguments::all
for included arguments. No default arguments will be added with
default_arguments::none
.
default_arguments::help
and
default_arguments::version
will individually add
--help
and
--version
.

Gathering Remaining Arguments

argparse
supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
$ compiler file1 file2 file3

To enable this, simply create an argument and mark it as

remaining
. All remaining arguments passed to argparse are gathered here.
argparse::ArgumentParser program("compiler");

program.add_argument("files") .remaining();

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

try { auto files = program.get<:vector>>("files"); std::cout << files.size() << " files provided" << std::endl; for (auto& file : files) std::cout << file << std::endl; } catch (std::logic_error& e) { std::cout << "No files provided" << std::endl; } </:vector>

When no arguments are provided:

$ ./compiler
No files provided

and when multiple arguments are provided:

$ ./compiler foo.txt bar.txt baz.txt
3 files provided
foo.txt
bar.txt
baz.txt

The process of gathering remaining arguments plays nicely with optional arguments too:

argparse::ArgumentParser program("compiler");

program.add_arguments("-o") .default_value(std::string("a.out"));

program.add_argument("files") .remaining();

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto output_filename = program.get<:string>("-o"); std::cout << "Output filename: " << output_filename << std::endl;

try { auto files = program.get<:vector>>("files"); std::cout << files.size() << " files provided" << std::endl; for (auto& file : files) std::cout << file << std::endl; } catch (std::logic_error& e) { std::cout << "No files provided" << std::endl; }

</:vector></:string>

$ ./compiler -o main foo.cpp bar.cpp baz.cpp
Output filename: main
3 files provided
foo.cpp
bar.cpp
baz.cpp

NOTE: Remember to place all optional arguments BEFORE the remaining argument. If the optional argument is placed after the remaining arguments, it too will be deemed remaining:

$ ./compiler foo.cpp bar.cpp baz.cpp -o main
5 arguments provided
foo.cpp
bar.cpp
baz.cpp
-o
main

Parent Parsers

Sometimes, several parsers share a common set of arguments. Rather than repeating the definitions of these arguments, a single parser with all the common arguments can be added as a parent to another ArgumentParser instance. The

.add_parents
method takes a list of ArgumentParser objects, collects all the positional and optional actions from them, and adds these actions to the ArgumentParser object being constructed:
argparse::ArgumentParser parent_parser("main");
parent_parser.add_argument("--parent")
  .default_value(0)
  .scan();

argparse::ArgumentParser foo_parser("foo"); foo_parser.add_argument("foo"); foo_parser.add_parents(parent_parser); foo_parser.parse_args({ "./main", "--parent", "2", "XXX" }); // parent = 2, foo = XXX

argparse::ArgumentParser bar_parser("bar"); bar_parser.add_argument("--bar"); bar_parser.parse_args({ "./main", "--bar", "YYY" }); // bar = YYY

Note You must fully initialize the parsers before passing them via

.add_parents
. If you change the parent parsers after the child parser, those changes will not be reflected in the child.

Further Examples

Construct a JSON object from a filename argument

argparse::ArgumentParser program("json_test");

program.add_argument("config") .action([](const std::string& value) { // read a JSON file std::ifstream stream(value); nlohmann::json config_json; stream >> config_json; return config_json; });

try { program.parse_args({"./test", "config.json"}); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

nlohmann::json config = program.get<:json>("config"); </:json>

Positional Arguments with Compound Toggle Arguments

argparse::ArgumentParser program("test");

program.add_argument("numbers") .nargs(3) .scan();

program.add_argument("-a") .default_value(false) .implicit_value(true);

program.add_argument("-b") .default_value(false) .implicit_value(true);

program.add_argument("-c") .nargs(2) .scan();

program.add_argument("--files") .nargs(3);

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto numbers = program.get<:vector>>("numbers"); // {1, 2, 3} auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get<:vector>>("-c"); // {3.14f, 2.718f} auto files = program.get<:vector>>("--files"); // {"a.txt", "b.txt", "c.txt"}

/// Some code that prints parsed arguments </:vector></:vector></:vector>

$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt
numbers = {1, 2, 3}
a = true
b = true
c = {3.14, 2.718}
files = {"a.txt", "b.txt", "c.txt"}

Restricting the set of values for an argument

argparse::ArgumentParser program("test");

program.add_argument("input") .default_value("baz") .action([](const std::string& value) { static const std::vector<:string> choices = { "foo", "bar", "baz" }; if (std::find(choices.begin(), choices.end(), value) != choices.end()) { return value; } return std::string{ "baz" }; });

try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); }

auto input = program.get("input"); std::cout << input << std::endl; </:string>

$ ./main fex
baz

Supported Toolchains

| Compiler | Standard Library | Test Environment | | :------------------- | :--------------- | :----------------- | | GCC >= 8.3.0 | libstdc++ | Ubuntu 18.04 | | Clang >= 7.0.0 | libc++ | Xcode 10.2 | | MSVC >= 14.16 | Microsoft STL | Visual Studio 2017 |

Contributing

Contributions are welcome, have a look at the CONTRIBUTING.md document for more information.

Contributors ✨

Thanks goes to these wonderful people:

svanveen
svanveen
Zhihao Yuan
Zhihao Yuan
Mio
Mio
zhihaoy
zhihaoy
Jack Clarke
Jack Clarke
Daniel Marshall
Daniel Marshall
mupp
mupp
Ethan Slattery
Ethan Slattery
skrobinson
skrobinson
Mike Zozu
Mike Zozu
Chuvi-w
Chuvi-w
KOLANICH
KOLANICH
Rafał Będźkowski
Rafał Będźkowski
Yoshihiro Hokazono
Yoshihiro Hokazono
Kenny Shen
Kenny Shen
The 42nd Mu00
The 42nd Mu00
Ubpa
Ubpa
Oliver Smith
Oliver Smith
Joseph Durel
Joseph Durel
rysson
rysson
aashwinr
aashwinr
bufferbase
bufferbase

License

The project is available under the MIT license.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.