English | 简体中文
A C++ Command Line Argument Parser, which want to provide more modern interfaces with C++23 features.
Inspired by clap and glaze, and heavily referenced clap for behaviors.
Get the name from clap's documentation:
-n, --name <NAME> Name of the person to greet
-c, --count <COUNT> Number of times to greet [default: 1]
-h, --help Print help
-V, --version Print version
- Greet uses C++23 features, so you need at least
gcc-13
,clang-17
or Visual Studio 2022 to compile it 😈 - I bet I haven't considered any performance optimizations 😱
- Your IDE/LSP may throw a lot of errors even though the code was compiled successfully 😭
- Greet has only one header file, You just need to download the
greet.hpp
file to your project and#include
it 😙 - Greet does not rely on any third-party libraries other than the
std
😍 - More modern interfaces might cheer you up 😆
Build the example
Make sure you have gcc-13
installed. Even if you plan to use clang-17
, you still need to install libstdc++-13-dev
.
The build command is simple:
g++ example.cpp -std=c++23 -o example # or
clang example.cpp -std=c++2b -stdlib=libc++ -o example
For MSVC(Visual Studio 2022), please select /std:c++latest
.
- For options that need an argument,
-a xxx
,-axxx
,-a=xxx
,--aaa xxx
and--aaa=xxx
are acceptable. - The short flag must be a printable character (from
!
to~
) and cannot be-
. - For options that don't need an argument,
-e -f -g
,--eee --fff --ggg
and-efg
are acceptable. - When you mix them, such as
-faxxxg
, it will be parsed as-f -a xxxg
but not-f -a xxx -g
. - If
-a
doesn't need an argument,-a-b
will be parsed as-a -- -b
then an error will be reported because of--
. But if-a
need an argument,-a-b
will be parsed to option-a
with its value-b
. - When the value of a option is start with a hyphen(
-
),-a-b
,-a=-b
and--aaa=-b
are acceptable. However,-a -b
and--aaa -b
is not acceptable by default, and will be parsed to two options. Ifallow_hyphen
is set,-a -b
and--aaa -b
can be accepted, check 4.1 available meta informations of NORMAL types to learn more. - Double-hyphen(
--
) means end of options, all subsequent arguments are no longer parsed. When input-a --
andallow_hyphen
of-a
flag is set, this will not be considered the end of options. All ignored arguments can be collected throughgreet::ignored
, check 3.5 IGNORED type to learn more.
Download the greet.hpp
file to your project and #include
it:
#include <greet.hpp>
You should declare an argument group class type, which should be: derived from greet::information
, default initializable and movable. Basically, you can declare no constructor and let the compiler handle it for you.
struct Args: public greet::information {
// don't declare any constructors
// version of program, will be printed at '-V' or '--version'
std::string version() override { return "greet v0.1.1"; }
// description of program, will be printed at '-h' or '--help'
std::string description() override { return "Greet with a person"; }
// we will learn about it soon
greet::meta genmeta() override { /* TODO */}
}
Add any options you want to provide to the user in the Args
:
struct Args: public greet::information {
std::string name;
size_t age;
bool greeted;
greet::counter times;
std::vector<std::string> places;
// version of program, will be printed at '-V' or '--version'
std::string version() override { return "greet v0.1.0"; }
// description of program, will be printed at '-h' or '--help'
std::string description() override { return "Greet with a person"; }
// we will learn about it soon
greet::meta genmeta() override { /* TODO */}
}
There are four different option types available:
The NORMAL type options include the integer types, the float point types, char
, const char *
and std::string
. If you want add your custom type, please refer to EXT: build your own NORMAL type option.
The NORMAL type options need an argument, that means, for an example, -a xx
.
The NORMAL type option cannot be used multuple times.
The BOOL type option can only be bool
.
The BOOL type option doesn't need an argument, true
if the user provides this option, false
otherwise.
The BOOL type option cannot be used multuple times.
The COUNTER type option can only be greet::counter
.
The COUNTER option doesn't need an argument, and can be used multiple times -- it's counting how many times the user has used this option. For an example, -a -a -a -a
makes the counter
to evaluate to 4
.
NOTE: value of greet::counter
can be implicitly converted to size_t
. You can use it as size_t
anywhere, for examples, to compare or print.
The VECTOR type options are std::vector
s when the template parameter is NORMAL type.
The VECTOR type options need an argument, and can be used multiple times. For an example, -a 1 -a 2 -a 3
will get a vector of 1
, 2
and 3
.
The IGNORED type option is not really an option and can only be greet::ignored
.
Double-hyphen(--
) means end of options, all subsequent arguments are no longer parsed. greet::ignored
can collect them. This is optional, if you don't care about those ignored arguments you don't need to provide a greet::ignored
.
The greet::ignored
type is a simple wrapper of std::vector<std::string>
, you can use it as std::vector<std::string>
anywhere.
You shouldn't provide more than 1 greet::ignored
, but don’t worry, it will be a compile-time error.
Write meta informations for each of your options:
struct Args: public greet::information {
std::string name;
size_t age;
bool greeted;
greet::counter times;
std::vector<std::string> places;
greet::ignored others;
...
greet::meta genmeta() override {
return {
greet::opt(name)
.shrt('n')
.lng("name")
.required()
.about("Name of the person to greet"),
greet::opt(age)
.lng("age")
.def(18u)
.about("Age of the person to greet"),
greet::opt(greeted)
.shrt('g')
.about("Have greeted before"),
greet::opt(times)
.shrt('t')
.about("How many times you want to greet"),
greet::opt(places)
.shrt('p')
.lng("place")
.allow_hyphen()
.about("Where to greet"),
greet::opt(others),
};
}
}
greet::opt(aaa) // bind to the `aaa` option
.shrt('a') // the short flag: '-a'
.lng("aaa") // the long flag: '--aaa'
// You should provide at least one of the two
.required() // user must provide this option
.def(0) // default value, only valid when didn't set `required`
// can construct the value in-place
.argname("aha") // the argument name when print help
// default to the uppercase long flag
// if no long flag, default to 'VALUE'
.allow_hyphen() // allow value start with a hyphen(`-`)
// this only affects `-a -b` and `--aaa -b`
.about("A NORMAL type option") // about message
greet::opt(aaa) // bind to the `aaa` option
.shrt('a') // the short flag: '-a'
.lng("aaa") // the long flag: '--aaa'
// You should provide at least one of the two
.about("A BOOL type option") // about message
greet::opt(aaa) // bind to the `aaa` option
.shrt('a') // the short flag: '-a'
.lng("aaa") // the long flag: '--aaa'
// You should provide at least one of the two
.about("A COUNTER type option") // about message
greet::opt(aaa) // bind to the `aaa` option
.shrt('a') // the short flag: '-a'
.lng("aaa") // the long flag: '--aaa'
// You should provide at least one of the two
.argname("aha") // the argument name when print help
// default to the uppercase long flag
// if no long flag, default to 'VALUE'
.allow_hyphen() // allow value start with a hyphen(`-`)
// this only affects `-a -b` and `--aaa -b`
.about("A VECTOR type option") // about message
No, no any meta informations for greet::ignored
type:
greet::opt(aaa) // bind to the `aaa` option
If you provide more than 1 greet::ignored
type option, a compile-time error will be raised.
NOTE: -h
, --help
, -V
and --version
are reserved for print help and version.
Call greet::greet()
with your argument group class as the template argument:
int main(int argc, char* argv[]) {
Args args = greet::greet<Args>(argc, argv);
}
int main(int argc, char* argv[]) {
Args args = greet::greet<Args>(argc, argv);
std::cout << "I will greet " << args.name << std::endl;
std::cout << "He/She is " << args.age << " years old" << std::endl;
if (args.greeted)
std::cout << "We have greeted before" << std::endl;
else
std::cout << "We never greeted before" << std::endl;
std::cout << "We should greet " << args.times << " times" << std::endl;
std::cout << "We may greet at " << args.places.size()
<< " places:" << std::endl;
for (const auto &place : args.places)
std::cout << "\t" << place << std::endl;
std::cout << "Other things:" << std::endl;
for (const auto &other : args.others)
std::cout << "\t" << other << std::endl;
}
$ example -h
greet with a person
Usage: example [OPTIONS] --name <NAME>
Options:
-n, --name <NAME> Name of the person to greet [REQUIRED]
--age <AGE> Age of the person to greet [default: 18]
-g Have greeted before
-t How many times you want to greet
-p, --place <PLACE> Where to greet
-h, --help Print help
-V, --version Print version
A NORMAL type option should be semiregular and string convertable.
To be semiregular, it basically means:
class myoption {
public:
myoption();
myoption(const myoption &);
myoption(myoption &&);
myoption &operator=(const myoption &);
myoption &operator=(myoption &&);
};
To be string convertable, specialize the greet::string_converter
with your type:
template <>
struct greet::string_converter<myoption> {
static auto from_str(const char *str)
-> std::expected<myoption, std::errc> {
/* TODO */
}
static std::string to_str(const myoption &value) {
/* TODO */
}
};
After that, you can use it as a NORMAL option type.