Skip to content

Cpp20 features

simfeo edited this page Aug 6, 2026 · 2 revisions

FancyArgumentParser works from C++11 onward, but a few things become available only under C++20. This page collects them; for the everyday task-oriented examples (which all work in C++11), see the Examples page.

Contents


Consuming the library as a module

On toolchains that support C++20 modules (MSVC, GCC ≥ 14) you can bring the whole library in with a single import instead of #include, via the argparse.ixx module interface shipped alongside the header:

import argparse;

#include <vector>
#include <string>
#include <iostream>

int main(int argc, char** argv)
{
    argparse::ArgumentParser parser("demo");
    parser.AddArgument(argparse::CreateNamedArgument("n", "name", 1,
        argparse::ArgTypeCast::e_String, true));

    auto obj = parser.ParseArgs(argc, argv);
    if (!obj.IsArgValid())
    {
        std::cout << obj.GetErrorString() << "\n";
        return 1;
    }
    std::cout << "Hello, " << obj.GetAsString("name") << "!\n";
    return 0;
}

The module simply wraps argparse.h and re-exports its public API, so import and #include give you exactly the same library — the header remains fully usable on its own. ARGPARSE_NAMESPACE_NAME works when building the module too.

Important — include order on MSVC. Put any standard-library #includes before the import argparse; line. Newer MSVC (VS 18 / 14.51+) reports C2572 "redefinition of default argument" if you import first and then include the same std headers the module already pulled in. Includes-before-import avoids it.

Building it — add argparse.ixx to your build as a module interface unit. For example with CMake (≥ 3.28) and a compiler that supports modules:

add_executable(app main.cpp)
target_sources(app
    PRIVATE
    FILE_SET argparse_module TYPE CXX_MODULES
    BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
    FILES ${CMAKE_CURRENT_SOURCE_DIR}/argparse.ixx)
target_compile_features(app PRIVATE cxx_std_20)

If your toolchain does not support modules (e.g. some Clang/AppleClang versions), just keep using #include "argparse.h" — nothing else changes.

Keyword arguments (designated initializers)

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 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 countArg = obj.GetArg("count");
    int count = countArg.GetArgumentExists() ? countArg.GetAsInt() : 1;
    for (int i = 0; i < count; ++i)
        std::cout << "Hello, " << obj.GetAsString("name") << "!\n";
    return 0;
}
>>> greet --name World --count 2
Hello, World!
Hello, World!

The full field list, in declaration order:

Struct Fields
NamedArgSpec shortName, longName, nargs, type, required, help, validator, validator_message, choices, pattern, default_value
PositionalArgSpec name, nargs, type, required, help, validator, validator_message, choices, pattern, default_value

choices (a std::vector<std::string>, parsed to type), pattern (a regex), and default_value (a std::any, C++17+) let you set those inline instead of chaining SetChoices / SetPattern / SetDefault:

parser.AddArgument(argparse::CreateNamedArgument({
    .longName = "port", .type = argparse::ArgTypeCast::e_int, .required = false,
    .choices = {"80", "443", "8080"},
    .default_value = 8080}));

The validator / validator_message fields let you attach a validator inline (see Choices and validators on the Examples page):

parser.AddArgument(argparse::CreateNamedArgument({
    .shortName = "m", .longName = "mode", .required = false,
    .validator = [](const std::string& v){ return v == "fast" || v == "safe"; },
    .validator_message = "mode must be 'fast' or 'safe'"}));

Notes:

  • Field order in the designated initializer must follow the struct's declaration order above. Fields you omit take their defaults.
  • SetChoices is not a spec field — chain it on the returned argument (with an explicit std::vector<std::string> to pick the right overload).
  • The same structs also work with ordinary aggregate initialization in C++11/14/17 — just supply the leading fields positionally instead of by name.

The parser itself has a matching aggregate, ParserSpec:

auto parser = argparse::ArgumentParser(argparse::ParserSpec{
    .name        = "cptool",
    .description = "Copy files",
    .allowAbbrev = false});

Post-processing parsed values with std::ranges

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.GetAsVecInt("nums");

    // 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

Back to the Examples page.

Clone this wiki locally