CPP-AP 2.2.6
Command-line argument parser for C++20
Loading...
Searching...
No Matches
Tutorial


Important:

This tutorial covers the most common use cases and features of the library. For more in-depth information and advanced usage, please refer to the full documentation. Instructions for building the documentation are available in the Dev Notes page.


Setting Up CPP-AP

CMake Integration

For CMake projects you can simply fetch the library in your CMakeLists.txt file:

cmake_minimum_required(VERSION 3.12)
project(my_project LANGUAGES CXX)
# Include FetchContent module
include(FetchContent)
# Fetch CPP-AP library
FetchContent_Declare(
cpp-ap
GIT_REPOSITORY https://github.com/SpectraL519/cpp-ap.git
GIT_TAG master # here you can specify the desired tag or branch
)
FetchContent_MakeAvailable(cpp-ap)
# Define the executable for the project
add_executable(my_project main.cpp)
set_target_properties(my_project PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED YES
)
# Link against the cpp-ap (v2) library
target_link_libraries(my_project PRIVATE cpp-ap-2)

Downloading the Library

If you do not use CMake you can dowload the desired library release, extract it in a desired directory and simply add <cpp-ap-dir>/include to the include directories of your project.




The Parser Class

To use the argument parser in your code you need to use the ap::argument_parser class.

The parameters you can specify for a parser's instance are:

  • Program name and description - used in the parser's configuration output (std::cout << parser).
  • Verbosity mode - false by default; if set to true the parser's configuration output will include more detailed info about arguments' parameters in addition to their names and help messages.
  • Arguments - specify the values/options accepted by the program.
parser.program_name("Name of the program")
.program_description("Description of the program")
.verbose();
Main argument parser class.
argument_parser & program_description(std::string_view description) noexcept
Set the program description.
argument_parser & program_name(std::string_view name) noexcept
Set the program name.
argument_parser & verbose(const bool v=true) noexcept
Set the verbosity mode.




Adding Arguments

The parser supports both positional and optional arguments. Both argument types are identified by their names represented as strings. Arguments can be defined with only a primary name or with a primary and a secondary (short) name.

Note:

The basic rules of parsing positional and optional arguments are described in the Parsing arguments section.

To add an argument to the parameter's configurations use the following syntax:

parser.add_<positional/optional>_argument<value_type>("argument");

or

parser.add_<positional/optional>_argument<value_type>("argument", "a");

Note:

The library supports any argument value types which meet the following requirements:

  • The std::ostream& operator<< is overloaded for the value type
  • The value type has a copy constructor and an assignment operator

Important:

If the value_type is not provided, std::string will be used.

You can also add boolean flags:

parser.add_flag("enable_some_option", "eso").help("enables option: some option");
/* equivalent to:
parser.add_optional_argument<bool>("enable_some_option", "eso")
.default_value(false)
.implicit_value(true)
.nargs(0)
.help("enables option: some option");
*/
argument::optional< bool > & add_flag(std::string_view primary_name)
Adds a boolean flag argument (an optional argument with value_type = bool) to the parser's configurat...

Boolean flags store true by default but you can specify whether the flag should store true or false when used:

parser.add_flag<false>("disable_another_option", "dao").help("disables option: another option");
/* equivalent to:
parser.add_optional_argument<bool>("disable_another_option", "dao")
.default_value(true)
.implicit_value(false)
.nargs(0)
.help("disables option: another option");
*/




Argument Parameters

Common Parameters

Parameters which can be specified for both positional and optional arguments include:

1. help - The argument's description which will be printed when printing the parser class instance.

parser.add_positional_argument<std::size_t>("number", "n")
.help("a positive integer value");
argument::positional< T > & add_positional_argument(std::string_view primary_name)
Adds a positional argument to the parser's configuration.

2. choices - A list of valid argument values.

The choices parameter takes as an argument an instance of std::initializer_list or any std::ranges::range type such that its value type is convertible to the argument's value_type.

parser.add_optional_argument<char>("method", "m").choices({'a', 'b', 'c'});
argument::optional< T > & add_optional_argument(std::string_view primary_name)
Adds a positional argument to the parser's configuration.

Important:

The choices function can be used only if the argument's value_type is equality comparable (defines the == operator).

3. Value actions - Function performed after parsing an argument's value.

Actions are represented as functions, which take the argument's value as an argument. The available action types are:

  • observe actions | void(const value_type&) - applied to the parsed value. No value is returned - this action type is used to perform some logic on the parsed value without modifying it.

    void is_valid_user_tag(const std::string& tag) {
    if (tag.empty() or tag.front() != '@')
    throw std::runtime_error(std::format("Invalid user tag: `{}` — must start with '@'", tag));
    }
    parser.add_optional_argument<std::string>("user", "u")
    .action<ap::action_type::observe>(is_valid_user_tag);
  • transform actions | value_type(const value_type&) - applied to the parsed value. The returned value will be used to initialize the argument's value.

    std::string to_lower(std::string s) {
    for (auto& c : s)
    c = static_cast<char>(std::tolower(c));
    return s;
    }
    parser.add_optional_argument<std::string>("key", "k")
    .action<ap::action_type::transform>(to_lower);
  • modify actions | void(value_type&) - applied to the initialized value of an argument.

    void capitalize(std::string& s) {
    s.at(0) = std::toupper(s.at(0));
    }
    parser.add_optional_argument<std::string>("name", "n")
    .action<ap::action_type::modify>(capitalize);

Tip:

A single argument can have multiple value actions. Instead of writing complex logic in one action, consider composing several simple, focused actions for better readability and reuse.


Parameters Specific for Optional Arguments

Apart from the common parameters listed above, for optional arguments you can also specify the following parameters:

1. required - If this option is set for an argument and it's value is not passed in the command-line, an exception will be thrown.

parser.add_optional_argument("output", "o").required();

2. bypass_required - If this option is set for an argument, parsing it's value will overrite the required option for other optional arguments and all positional arguments.

3. nargs - Sets the allowed number of values to be parsed for an argument. This can be set as a:

Important:

The default nargs parameter value is ap::nargs::any().

4. default_value - The default value for an argument which will be used if no values for this argument are parsed

parser.add_opitonal_argument("output", "o").default_value("out.txt");

5. implicit_value - A value which will be set for an argument if only it's flag is parsed from the command-line but no values follow.

// program.cpp
parser.add_optional_argument("save", "s")
.implicit_value("out.txt")
.help("save the program's output to a file");

In this example if you run the program with only a -s or --save flag and no value, the value will be set to out.txt.

6. On-flag actions - For optional arguments, apart from value actions, you can specify on-flag actions which are executed immediately after parsing an argument's flag.

void print_debug_info() noexcept {
#ifdef NDEBUG
std::cout << "Running in release mode.\n";
#else
std::cout << "Running in debug mode.\n";
#endif
std::exit(EXIT_SUCCESS);
};
parser.add_optional_argument("--debug-info")
.action<ap::action_type::on_flag>(print_debug_info);
An on-flag action specifier.

Here the print_debug_info function will be called right after parsing the --debug-info flag and the program will exit, even if there are more arguments after this flag.




Predefined Parameter Values

Actions

  • print_config | on-flag

    Prints the configuration of the parser to the output stream and optionally exits with the given code.

    typename ap::action_type::on_flag::type print_config(
    const ap::argument_parser& parser,
    const std::optional<int> exit_code = std::nullopt,
    std::ostream& os = std::cout
    ) noexcept;
  • check_file_exists | observe (value type: std::string)

    Throws if the provided file path does not exist.

    detail::callable_type<ap::action_type::observe, std::string> check_file_exists() noexcept;
  • gt | observe (value type: arithmetic)

    Validates that the value is strictly greater than lower_bound.

    template <ap::detail::c_arithmetic T>
    detail::callable_type<ap::action_type::observe, T> gt(const T lower_bound) noexcept;
  • geq | observe (value type: arithmetic)

    Validates that the value is greater than or equal to lower_bound.

    template <ap::detail::c_arithmetic T>
    detail::callable_type<ap::action_type::observe, T> geq(const T lower_bound) noexcept;
  • lt | observe (value type: arithmetic)

    Validates that the value is strictly less than upper_bound.

    template <ap::detail::c_arithmetic T>
    detail::callable_type<ap::action_type::observe, T> lt(const T upper_bound) noexcept
  • leq | observe (value type: arithmetic)

    Validates that the value is less than or equal to upper_bound.

    template <ap::detail::c_arithmetic T>
    detail::callable_type<ap::action_type::observe, T> leq(const T upper_bound) noexcept
  • within | observe (value type: arithmetic)

    Checks if the value is within the given interval. Bound inclusivity is customizable using template parameters.

    template <ap::detail::c_arithmetic T, bool LeftInclusive = true, bool RightInclusive = true>
    detail::callable_type<ap::action_type::observe, T> within(
    const T lower_bound, const T upper_bound
    ) noexcept




Default Arguments

The CPP-AP library defines several default arguments, which can be added to the parser's configuration as follows.

// here `...` represents a collection of ap::argument::default_positional values
// here `...` represents a collection of ap::argument::default_optional values
argument_parser & default_positional_arguments(const AR &arg_discriminator_range) noexcept
Set default positional arguments.

Note:

These functions work with std::initializer_list and all other std::ranges::range types with the correct value type - ap::argument::default_{positional/optional}

The available default arguments are:

  • default_positional::input:

    // equivalent to:
    parser.add_positional_argument<std::string>("input")
    .action<ap::action_type::modify>(ap::action::check_file_exists())
    .help("Input file path");
  • default_positional::output:

    // equivalent to:
    parser.add_positional_argument("output").help("Output file path");
  • default_optional::help:

    // equivalent to:
    parser.add_flag("help", "h")
    .action<action_type::on_flag>(action::print_config(arg_parser, EXIT_SUCCESS))
    .help("Display the help message");
  • default_optional::input and default_optional::multi_input:

    // input - equivalent to:
    parser.add_optional_argument("input", "i")
    .required()
    .nargs(1)
    .action<ap::action_type::observe>(ap::action::check_file_exists())
    .help("Input file path");
    // multi_input - equivalent to:
    parser.add_optional_argument("input", "i")
    .required()
    .nargs(ap::nargs::at_least(1))
    .action<ap::action_type::observe>(ap::action::check_file_exists())
    .help("Input files paths");
    An observing value action specifier.
  • default_optional::output and default_optional::multi_output:

    // output - equivalent to:
    parser.add_optional_argument("output", "o")
    .required()
    .nargs(1)
    .help("Output file path");
    // multi_otput - equivalent to:
    parser.add_optional_argument("output", "o")
    .required()
    .nargs(ap::nargs::at_least(1))
    .help("Output files paths");




Parsing Arguments

To parse the command-line arguments use the void argument_parser::parse_args(const AR& argv) method, where AR must be a type that satisfies std::ranges::range and its value type is convertible to std::string.

The argument_parser class also defines the void parse_args(int argc, char* argv[]) overload, which works directly with the argc and argv arguments of the main function.

Important:

The parse_args(argc, argv) method ignores the first argument (the program name) and is equivalent to calling:

parse_args(std::span(argv + 1, argc - 1));

Tip:

The parse_args function may throw an ap::argument_parser_exception (specifically the ap::parsing_failure derived exception) if the provided command-line arguments do not match the expected configuration. To simplify error handling, the argument_parser class provides try_parse_args methods, which automatically catch these exceptions, print the error message, and exit with a failure status.

Internally, This is equivalent to:

try {
parser.parse_args(...);
}
catch (const ap::argument_parser_exception& err) {
std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl;
std::exit(EXIT_FAILURE);
}
void parse_args(int argc, char *argv[])
Parses the command-line arguments.
Base type for the argument parser functionality errors/exceptions.
// power.cpp
#include <cmath>
#include <iostream>
int main(int argc, char* argv[]) {
// create the parser class instance
parser.program_name("power calculator")
.program_description("Calculates the value of an expression: base ^ exponent");
// add arguments
parser.add_positional_argument<double>("base").help("the exponentation base value");
parser.add_optional_argument<int>("exponent", "e")
.nargs(ap::nargs::any())
.help("the exponent value");
parser.default_optional_arguments({ap::argument::default_optional::help});
// parse command-line arguments
parser.try_parse_args(argc, argv);
// check if any values for the `exponent` argument have been parsed
if (not parser.has_value("exponent")) {
std::cout << "no exponent values given" << std::endl;
std::exit(EXIT_SUCCESS);
}
const double base = parser.value<double>("base");
const std::vector<int> exponent_values = parser.values<int>("exponent");
for (const int exponent : exponent_values) {
std::cout << base << " ^ " << exponent << " = " << std::pow(base, exponent) << std::endl;
}
return 0;
}
// compiled with:
// g++ -o power power.cpp -I <cpp-ap-include-dir> -std=c++20
Main library header file. Defines the argument_parser class.
T value(std::string_view arg_name) const
bool has_value(std::string_view arg_name) const noexcept
std::vector< T > values(std::string_view arg_name) const
argument_parser & default_optional_arguments(const AR &arg_discriminator_range) noexcept
Set default optional arguments.
void try_parse_args(int argc, char *argv[])
Parses the command-line arguments and exits on error.

Argument Parsing Rules:

  • Positional arguments are parsed first, in the order they were defined in and without a flag.

    In the example above the first command-line argument must be the value for the positional argument:

    ./power 2
    # out:
    # no exponent values given
    ./power
    # out:
    # [ERROR] : No values parsed for a required argument [base]
    # Program: power calculator
    #
    # Calculates the value of an expression: base ^ exponent
    #
    # Positional arguments:
    #
    # base : the exponentation base value
    #
    # Optional arguments:
    #
    # --exponent, -e : the exponent value
    # --help, -h : Display the help message

Important:

For each positional argument there must be exactly one value.

  • Optional arguments are parsed only with a flag:

    ./power 2 --exponent 1 2 3
    # equivalent to: ./power 2 -e 1 2 3
    # out:
    # 2 ^ 1 = 2
    # 2 ^ 2 = 4
    # 2 ^ 3 = 8

    You can use the flag for each command-line value:

    ./power 2 -e 1 -e 2 -e 3

    Not using a flag will result in an error:

    ./power 2 1 2 3
    # out:
    # [ERROR] : Failed to deduce the argument for values [1, 2, 3]
    # Program: power calculator
    #
    # Calculates the value of an expression: base ^ exponent
    #
    # Positional arguments:
    #
    # base : the exponentation base value
    #
    # Optional arguments:
    #
    # --exponent, -e : the exponent value
    # --help, -h : Display the help message

Important:

The parser behaviour depends on the argument definitions. The argument parameters are described int the Argument parameters section.




Retrieving Argument Values

You can retrieve the argument's value with:

(const) auto value = parser.value<value_type>("argument_name"); // (1)
(const) auto value = parser.value_or<value_type>("argument_name", default_value); // (2)
T value_or(std::string_view arg_name, U &&default_value) const
  1. This will return the value parsed for the given argument.

    For optional arguments this will return the argument's predefined value if no value has been parsed. Additionaly, if more than one value has been parsed for an optional argument, this function will return the first parsed value.

  2. When a value has been parsed for the argument, the behaviour is the same as in case **(1)**. Otherwise, this will return value_type{std::forward<U>(default_value)} (where U is the deducted type of default_value), if:
    • There is no value parsed for a positional argument
    • There is no parsed values and no predefined values for an optional arrument


Additionally for optional arguments, you can use:

(const) std::vector<value_type> values = parser.values<value_type>("argument_name");

which returns a vector containing all values parsed for the given argument.




Examples

The library usage examples / demo projects can be found in the cpp-ap-demo repository.