-
Notifications
You must be signed in to change notification settings - Fork 0
Examples
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("Program name").SetDescription("Description of program");
parser.AddArgument(argparse::CreateNamedArgument()
.SetLongName("numbers")
.SetAnyNumberOfArgumentsButAtLeastOne()
.SetType(argparse::ArgTypeCast::e_int));
parser.AddArgument(argparse::CreateNamedArgument()
.SetLongName("some_boring_long_name")
.SetAnyNumberOfArgumentsButAtLeastOne()
.SetType(argparse::ArgTypeCast::e_int)
.SetHelp("some_boring_long_name description with some important information for user.")
.SetRequired(false));
auto obj = parser.ParseArgs(argc, argv);
if (obj.IsArgValid())
{
auto arg = obj.GetArg("numbers");
if (arg.GetArgumentExists())
{
for (auto& el : arg.GetAsVecInt())
{
std::cout << el << std::endl;
}
}
}
else
{
std::string help = parser.GetHelp(80);
std::cout << obj.GetErrorString() << std::endl;
std::cout << help << std::endl;
}
return 0;
}This example creates a program with the keys -n, --numbers, -s, --some_boring_long_name, -h and --help. The help is auto-generated. The types of numbers and some_boring_long_name are integers, each accepting one or more values.
Typical output without any arguments:
main.cpp [-n,--numbers [n ...] ] [-s,--some_boring_long_name [s ...] ] -h,--help
Description of program
optional arguments:
-n,--numbers some numbers description with some important information
for user. Type: INT. Args count: at least one.
-s,--some_boring_long_name
some_boring_long_name description with some important information
for user. Type: INT. Args count: at least one.
-h,--help Show help!
Note actually terminal cannot get infinite numbers of arguments. In most cases maximum length of all input that terminal can pass is limited with 8 kb.
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser(__FILE__).SetDescription("Description of program");
parser.AddArgument(argparse::CreateNamedArgument("n", "numbers", argparse::kFromOneToInfiniteArgCount,
argparse::ArgTypeCast::e_int, false,
"some numbers description with some important information for user."));
parser.AddArgument(argparse::CreateNamedArgument("s", "some_boring_long_name", argparse::kFromOneToInfiniteArgCount,
argparse::ArgTypeCast::e_int, false,
"some_boring_long_name description with some important information for user."));
auto obj = parser.ParseArgs(argc, argv);
if (obj.IsArgValid())
{
auto arg = obj.GetArg("numbers");
if (arg.GetArgumentExists())
{
for (auto& el : arg.GetAsVecInt())
{
std::cout << el << std::endl;
}
}
}
else
{
std::string help = parser.GetHelp(80);
std::cout << obj.GetErrorString() << std::endl;
std::cout << help << std::endl;
}
return 0;
}
C++20 keyword style — the same two arguments written with designated initializers (requires C++20; the rest of the program is unchanged):
parser.AddArgument(argparse::CreateNamedArgument({
.longName = "numbers",
.nargs = argparse::kFromOneToInfiniteArgCount,
.type = argparse::ArgTypeCast::e_int}));
parser.AddArgument(argparse::CreateNamedArgument({
.longName = "some_boring_long_name",
.nargs = argparse::kFromOneToInfiniteArgCount,
.type = argparse::ArgTypeCast::e_int,
.required = false,
.help = "some_boring_long_name description with some important information for user."}));the generic -h, --help option is suppressed example compiles for c++17 and uses std::any approach
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("main").SetDescription("ArgParse example");
parser.AddArgument(argparse::CreateNamedArgument("b", "b_key", 1, argparse::ArgTypeCast::e_int, true,
R"=(some "b_key" description with some important information for user)="));
parser.SetAddHelp(false);
auto obj = parser.ParseArgs(argc, argv);
if (obj.IsArgValid())
{
auto arg = obj.GetArg("b_key");
std::cout << arg.Get().type().name() << ": " << std::any_cast<int>(arg.Get()) << std::endl;
}
else
{
std::string help = parser.GetHelp(80);
std::cout << obj.GetErrorString() << std::endl;
std::cout << help << std::endl;
}
return 0;
}You can change the 1 to 0 in this example. It will then be a flag, and you cannot access its content.
C++20 keyword style (same behaviour):
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "b",
.longName = "b_key",
.nargs = 1,
.type = argparse::ArgTypeCast::e_int,
.required = true,
.help = R"=(some "b_key" description with some important information for user)="}));A positional argument is passed as a raw argument without any key. That is the only difference between keyed and positional arguments.
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("main").SetDescription("ArgParse example");
parser.AddArgument(argparse::CreatePositionalArgument("int1")
.SetType(argparse::ArgTypeCast::e_int).SetRequired(false));
auto obj = parser.ParseArgs(argc, argv);
if (obj.IsArgValid())
{
auto arg = obj.GetArg("int1");
if (arg.GetArgumentExists())
{
std::cout << arg.Get().type().name() << ": " << std::any_cast<int>(arg.Get()) << std::endl;
}
}
else
{
std::string help = parser.GetHelp(80);
std::cout << obj.GetErrorString() << std::endl;
std::cout << help << std::endl;
}
return 0;
}
C++20 keyword style (same behaviour):
parser.AddArgument(argparse::CreatePositionalArgument({
.name = "int1",
.type = argparse::ArgTypeCast::e_int,
.required = false}));#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("main").SetDescription("ArgParse example");
parser.AddArgument(argparse::CreatePositionalArgument("nums").SetType(argparse::ArgTypeCast::e_double).SetNumberOfArguments(2));
parser.AddArgument(argparse::CreateNamedArgument("o", "operation").SetRequired(true).SetChoices({"+","-","*","/"}));
parser.SetEpilogue("This is example of epilogue. Will be placed in the end of help");
auto obj = parser.ParseArgs(argc, argv);
if (obj.IsArgValid())
{
auto arg = obj.GetArg("nums");
auto op = obj.GetArg("operation");
const std::vector<double>& nums = arg.GetAsVecDouble();
const std::string& operation = op.GetAsString();
std::cout << nums[0] << operation << nums[1] << "=";
if (operation == "+")
{
std::cout << nums[0] + nums[1] << std::endl;
}
else if (operation == "-")
{
std::cout << nums[0] - nums[1] << std::endl;
}
else if (operation == "*")
{
std::cout << nums[0] * nums[1] << std::endl;
}
else //if(operation == "/")
{
std::cout << nums[0] / nums[1] << std::endl;
}
}
else
{
std::string help = parser.GetHelp(80);
std::cout << obj.GetErrorString() << std::endl;
std::cout << help << std::endl;
}
return 0;
}usage examples
>>> calc.exe 12121 222 -o +
12121+222=12343
>>> calc.exe 12121 222 -o %
Value '%' is out of choices for "operation"
main nums [nums nums] -o,--operation [{+, -, *, /}] [-h,--help]
ArgParse example
positional arguments:
nums Type: DOUBLE. Args count: 2
named arguments:
-o,--operation Type: STRING. Choices:+, -, *, /. Args count: 1
-h,--help Show help!
This is example of epilogue. Will be placed in the end of help
C++20 keyword style (same behaviour). SetChoices is not part of the spec
struct, so chain it afterwards; use an explicit std::vector<std::string> to
disambiguate the overload when calling it on the freshly-built argument:
parser.AddArgument(argparse::CreatePositionalArgument({
.name = "nums",
.nargs = 2,
.type = argparse::ArgTypeCast::e_double}));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "o",
.longName = "operation",
.required = true})
.SetChoices(std::vector<std::string>{"+", "-", "*", "/"}));A flag holds no value — it is either present or not — created with SetArgumentIsFlag().
Remember that every argument is required by default, so an optional flag must opt
out with SetRequired(false). SetDefault(...) gives an optional argument a value to
fall back on when the user omits it.
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("build").SetDescription("Flags and default values");
parser.AddArgument(argparse::CreateNamedArgument("v", "verbose").SetArgumentIsFlag()
.SetRequired(false)
.SetHelp("Enable verbose output"));
parser.AddArgument(argparse::CreateNamedArgument("j", "jobs", 1, argparse::ArgTypeCast::e_int, false)
.SetDefault(1)
.SetHelp("Number of parallel jobs (default: 1)"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
const bool verbose = obj.GetArg("verbose").GetArgumentExists();
const int jobs = obj.GetArg("jobs").GetAsInt(); // always present thanks to SetDefault
std::cout << "verbose = " << (verbose ? "true" : "false") << "\n";
std::cout << "jobs = " << jobs << std::endl;
return 0;
}>>> build -v
verbose = true
jobs = 1
>>> build
verbose = false
jobs = 1
C++20 keyword style (same behaviour). A flag is just nargs = 0; SetDefault
is chained since it is not a spec field:
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "v", .longName = "verbose",
.nargs = 0, .required = false, .help = "Enable verbose output"}));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "j", .longName = "jobs",
.type = argparse::ArgTypeCast::e_int, .required = false,
.help = "Number of parallel jobs (default: 1)"})
.SetDefault(1));Positional and named arguments can be combined freely. Below, a small cp-like tool
takes two positionals (source, dest) and one named flag (-f/--force).
Important: getters that return a reference —
GetAsString()and theGetAsVec*()family — return a reference into theArgumentParsedobject. Store the result ofGetArg(...)in a variable before calling them, otherwise the reference dangles. (Value getters likeGetAsInt()/GetAsDouble()return by value and are always safe.)
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("mycp").SetDescription("Copy SOURCE to DEST");
parser.AddArgument(argparse::CreatePositionalArgument("source").SetHelp("File to copy from"));
parser.AddArgument(argparse::CreatePositionalArgument("dest").SetHelp("File to copy to"));
parser.AddArgument(argparse::CreateNamedArgument("f", "force").SetArgumentIsFlag()
.SetRequired(false)
.SetHelp("Overwrite destination if it exists"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
// Store the ArgumentParsed before reading string references from it.
auto sourceArg = obj.GetArg("source");
auto destArg = obj.GetArg("dest");
const std::string& src = sourceArg.GetAsString();
const std::string& dst = destArg.GetAsString();
const bool force = obj.GetArg("force").GetArgumentExists();
std::cout << "copy " << src << " -> " << dst << (force ? " (force)" : "") << std::endl;
return 0;
}>>> mycp a.txt b.txt -f
copy a.txt -> b.txt (force)
C++20 keyword style (same behaviour):
parser.AddArgument(argparse::CreatePositionalArgument({.name = "source", .help = "File to copy from"}));
parser.AddArgument(argparse::CreatePositionalArgument({.name = "dest", .help = "File to copy to"}));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "f", .longName = "force",
.nargs = 0, .required = false, .help = "Overwrite destination if it exists"}));There are two distinct kinds of errors:
-
Setup errors — a malformed definition, e.g. an argument with neither a name nor a
positional name.
AddArgumentthrows for these; they are programmer mistakes. You only need atry/catchif you build arguments dynamically from external data. -
Input errors — bad user input (missing required argument, wrong type, value out of
choices). These are never thrown; they are reported through the result object via
IsArgValid()andGetErrorString().
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
argparse::ArgumentParser parser("app");
try
{
parser.AddArgument(argparse::CreateNamedArgument("p", "port", 1, argparse::ArgTypeCast::e_int, true)
.SetHelp("Port to listen on"));
}
catch (const std::exception& e)
{
std::cerr << "argument setup error: " << e.what() << std::endl;
return 2;
}
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cerr << "error: " << obj.GetErrorString() << "\n\n";
std::cerr << parser.GetHelp(80) << std::endl;
return 1;
}
std::cout << "listening on port " << obj.GetArg("port").GetAsInt() << std::endl;
return 0;
}>>> app -p 8080
listening on port 8080
>>> app
error: Required argument with name "port" does not exist
app -p,--port [p] [-h,--help]
named arguments:
-p,--port Port to listen on Type: INT. Args count: 1
-h,--help Show help!
C++20 keyword style (same behaviour):
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "p", .longName = "port",
.nargs = 1, .type = argparse::ArgTypeCast::e_int, .required = true,
.help = "Port to listen on"}));Combines a typed positional (double), two required named arguments constrained with
SetChoices, and an epilogue.
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("convert").SetDescription("Convert a temperature between units");
parser.AddArgument(argparse::CreatePositionalArgument("value")
.SetType(argparse::ArgTypeCast::e_double).SetHelp("Temperature value"));
parser.AddArgument(argparse::CreateNamedArgument("f", "from").SetRequired(true)
.SetChoices({"C", "F", "K"}).SetHelp("Source unit"));
parser.AddArgument(argparse::CreateNamedArgument("t", "to").SetRequired(true)
.SetChoices({"C", "F", "K"}).SetHelp("Target unit"));
parser.SetEpilogue("Units: C = Celsius, F = Fahrenheit, K = Kelvin");
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
auto fromArg = obj.GetArg("from");
auto toArg = obj.GetArg("to");
const double v = obj.GetArg("value").GetAsDouble(); // returns by value: safe inline
const std::string& from = fromArg.GetAsString();
const std::string& to = toArg.GetAsString();
double c = from == "C" ? v : from == "F" ? (v - 32.0) * 5.0 / 9.0 : v - 273.15;
double out = to == "C" ? c : to == "F" ? c * 9.0 / 5.0 + 32.0 : c + 273.15;
std::cout << v << from << " = " << out << to << std::endl;
return 0;
}>>> convert 100 -f C -t F
100C = 212F
>>> convert 100 -f C -t Q
Value 'Q' is out of choices for "to"
C++20 keyword style (same behaviour; SetChoices chained as before):
parser.AddArgument(argparse::CreatePositionalArgument({
.name = "value", .type = argparse::ArgTypeCast::e_double, .help = "Temperature value"}));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "f", .longName = "from", .required = true, .help = "Source unit"})
.SetChoices(std::vector<std::string>{"C", "F", "K"}));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "t", .longName = "to", .required = true, .help = "Target unit"})
.SetChoices(std::vector<std::string>{"C", "F", "K"}));kAnyArgCount accepts zero or more values (use kFromOneToInfiniteArgCount to demand at
least one). SetPrefixChars('+') changes the option prefix, and SetIgnoreUnknownArgs(true)
lets the parser skip options it doesn't recognise instead of failing.
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("sum")
.SetDescription("Sum any amount of numbers")
.SetPrefixChars('+')
.SetIgnoreUnknownArgs(true);
parser.AddArgument(argparse::CreateNamedArgument("n", "nums", argparse::kAnyArgCount,
argparse::ArgTypeCast::e_int, false).SetHelp("Numbers to add"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
long long total = 0;
auto arg = obj.GetArg("nums");
if (arg.GetArgumentExists())
for (int n : arg.GetAsVecInt())
total += n;
std::cout << "sum = " << total << std::endl;
return 0;
}>>> sum ++nums 3 4 5 ++junk hello
sum = 12
C++20 keyword style (same behaviour):
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "n", .longName = "nums",
.nargs = argparse::kAnyArgCount, .type = argparse::ArgTypeCast::e_int,
.required = false, .help = "Numbers to add"}));If the default argparse namespace clashes with something in your project, define
ARGPARSE_NAMESPACE_NAME before including the header to rename it.
#define ARGPARSE_NAMESPACE_NAME cli
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = cli::ArgumentParser("greet").SetDescription("Custom namespace demo");
parser.AddArgument(cli::CreateNamedArgument("n", "name", 1, cli::ArgTypeCast::e_String, true)
.SetHelp("Who to greet"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
std::cout << "Hello, " << obj.GetArg("name").GetAsString() << "!" << std::endl;
return 0;
}>>> greet --name World
Hello, World!
C++20 keyword style (same behaviour; the spec factory lives in your renamed
namespace too, here cli::):
parser.AddArgument(cli::CreateNamedArgument({
.shortName = "n", .longName = "name",
.nargs = 1, .type = cli::ArgTypeCast::e_String, .required = true,
.help = "Who to greet"}));Use ArgTypeCast::e_bool for arguments whose value is a boolean. The accepted
spellings are true/True/TRUE and false/False/FALSE. (If you want a
valueless on/off switch rather than a typed value, use SetArgumentIsFlag() —
see Flags and default values above.)
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("feature").SetDescription("Bool-typed arguments");
parser.AddArgument(argparse::CreateNamedArgument("d", "debug", 1,
argparse::ArgTypeCast::e_bool, false).SetDefault(false)
.SetHelp("Enable debug mode (true/false)"));
// A bool argument can also take several values.
parser.AddArgument(argparse::CreateNamedArgument("s", "switches",
argparse::kFromOneToInfiniteArgCount, argparse::ArgTypeCast::e_bool, false)
.SetHelp("A series of on/off switches"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
std::cout << "debug = " << (obj.GetArg("debug").GetAsBool() ? "true" : "false") << "\n";
auto sw = obj.GetArg("switches");
if (sw.GetArgumentExists())
{
std::cout << "switches =";
for (bool b : sw.GetAsVecBool())
std::cout << ' ' << (b ? "on" : "off");
std::cout << std::endl;
}
return 0;
}>>> feature --debug true --switches true false TRUE
debug = true
switches = on off on
C++20 keyword style (same behaviour; SetDefault(false) chained):
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "d", .longName = "debug",
.nargs = 1, .type = argparse::ArgTypeCast::e_bool, .required = false,
.help = "Enable debug mode (true/false)"})
.SetDefault(false));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "s", .longName = "switches",
.nargs = argparse::kFromOneToInfiniteArgCount, .type = argparse::ArgTypeCast::e_bool,
.required = false, .help = "A series of on/off switches"}));By default the usage line is generated from your arguments. SetUsage(...)
replaces just that first line with your own wording; the positional/named
argument listings below it are still generated for you.
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("serve").SetDescription("Start a web server");
parser.SetUsage("serve --port PORT [--host HOST]");
parser.AddArgument(argparse::CreateNamedArgument("p", "port", 1,
argparse::ArgTypeCast::e_int, true).SetHelp("Port to listen on"));
parser.AddArgument(argparse::CreateNamedArgument("H", "host", 1,
argparse::ArgTypeCast::e_String, false).SetDefault(std::string("0.0.0.0"))
.SetHelp("Interface to bind"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
auto host = obj.GetArg("host");
std::cout << "listening on " << host.GetAsString()
<< ":" << obj.GetArg("port").GetAsInt() << std::endl;
return 0;
}>>> serve --port 8080
listening on 0.0.0.0:8080
>>> serve
Required argument with name "port" does not exist
serve --port PORT [--host HOST]
Start a web server
named arguments:
-p,--port Port to listen on Type: INT. Args count: 1
-H,--host Interface to bind Type: STRING. Args count: 1
-h,--help Show help!
C++20 keyword style (same behaviour; SetDefault chained):
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "p", .longName = "port",
.nargs = 1, .type = argparse::ArgTypeCast::e_int, .required = true,
.help = "Port to listen on"}));
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "H", .longName = "host",
.nargs = 1, .type = argparse::ArgTypeCast::e_String, .required = false,
.help = "Interface to bind"})
.SetDefault(std::string("0.0.0.0")));Tip: put positional arguments before named ones on the command line. The parser collects positionals first, so
mytool FILE --flagworks whilemytool --flag FILEmay misassignFILE.
The library compiles cleanly under C++11, C++14, C++17, C++20 and C++23. The
value getters (GetAsVecInt() and friends) return by value, so they compose
directly with C++20 range views — no dangling, no manual copies.
// Build with C++20: c++ -std=c++20 -I<path> stats.cpp
#include <iostream>
#include <ranges>
#include <vector>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("stats").SetDescription("C++20 ranges over parsed values");
parser.AddArgument(argparse::CreateNamedArgument("n", "nums",
argparse::kFromOneToInfiniteArgCount, argparse::ArgTypeCast::e_int, true)
.SetHelp("Integers to process"));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
auto nums = obj.GetArg("nums").GetAsVecInt();
// Keep even numbers and square them, lazily, via C++20 views.
auto evenSquares = nums
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
std::cout << "even squares:";
for (int v : evenSquares)
std::cout << ' ' << v;
std::cout << std::endl;
return 0;
}>>> stats --nums 1 2 3 4 5 6
even squares: 4 16 36
CreateNamedArgument and CreatePositionalArgument also accept an aggregate
spec (NamedArgSpec / PositionalArgSpec). With C++20 designated
initializers this reads like Python's add_argument(type=..., required=...)
— you name each field and skip the ones you don't need, instead of remembering
positional argument order.
// Build with C++20: c++ -std=c++20 -I<path> greet.cpp
#include <iostream>
#include "ArgParse/argparse.h"
int main(int argc, char** argv)
{
auto parser = argparse::ArgumentParser("greet").SetDescription("Keyword-style arguments (C++20)");
parser.AddArgument(argparse::CreateNamedArgument({
.shortName = "n",
.longName = "name",
.nargs = 1,
.type = argparse::ArgTypeCast::e_String,
.required = true,
.help = "Who to greet"}));
parser.AddArgument(argparse::CreateNamedArgument({
.longName = "count",
.type = argparse::ArgTypeCast::e_int,
.required = false,
.help = "How many times"}));
auto obj = parser.ParseArgs(argc, argv);
if (!obj.IsArgValid())
{
std::cout << obj.GetErrorString() << "\n" << parser.GetHelp(80) << std::endl;
return 1;
}
auto name = obj.GetArg("name");
auto countArg = obj.GetArg("count");
int count = countArg.GetArgumentExists() ? countArg.GetAsInt() : 1;
for (int i = 0; i < count; ++i)
std::cout << "Hello, " << name.GetAsString() << "!\n";
return 0;
}>>> greet --name World --count 2
Hello, World!
Hello, World!
Note: the field order in the designated initializer must follow the struct's declaration order (
shortName, longName, nargs, type, required, helpforNamedArgSpec). Fields you omit take their defaults. The same structs also work with ordinary aggregate initialization in C++11/14/17.