Skip to content

rfl::cli — Command-Line Argument Parser

Parse argc/argv into any reflectable struct via rfl::cli::read<T>(argc, argv).

Usage

#include <rfl/cli.hpp>

struct Config {
  std::string host_name;
  int port;
  bool verbose;
  std::optional<double> rate;
  std::vector<std::string> tags;
};

int main(int argc, char* argv[]) {
  const auto result = rfl::cli::read<Config>(argc, argv);
  // ./app --host-name=localhost --port=8080 --verbose --tags=a,b,c
}

Field names undergo automatic snake_case -> kebab-case conversion: host_name matches --host-name.

Positional arguments

Wrap a field with rfl::Positional<T> to accept it as a bare (non-flag) argument:

struct Config {
  rfl::Positional<std::string> input_file;
  rfl::Positional<std::string> output_file;
  bool verbose;
};

// ./app input.txt output.txt --verbose

Positional arguments are matched in declaration order. They can also be passed as named arguments: --input-file=input.txt.

The -- separator forces all subsequent tokens into positional:

./app --verbose -- --not-a-flag.txt

Short aliases

Wrap a field with rfl::Short<"x", T> to add a single-character alias:

struct Config {
  rfl::Short<"p", int> port;
  rfl::Short<"v", bool> verbose;
  std::string host;
};

// ./app -p 8080 -v --host=localhost
// ./app -p=8080 -v --host=localhost
// ./app --port=8080 --verbose --host=localhost   (long names still work)

Short bool flags do not consume the next token as a value — -v somefile treats somefile as a positional argument, not as the value of -v. To explicitly set a bool short flag, use = syntax: -v=true, -v=false.

Combining Positional and Short

Positional and Short can be used together in the same struct, but cannot be nested (Positional<Short<...>> is a compile-time error):

struct Config {
  rfl::Positional<std::string> input_file;
  rfl::Short<"o", std::string> output_dir;
  rfl::Short<"v", bool> verbose;
  int count;
};

// ./app data.csv -o /tmp/out -v --count=10

Supported types

Type CLI format Notes
std::string --key=value
int, long, ... --key=42
float, double --key=1.5
bool --flag or --flag=true No = implies true
enum --key=value_name Via rfl::string_to_enum
std::optional<T> omit for nullopt
std::vector<T> --key=a,b,c Comma-separated; empty elements skipped
Nested struct --parent.child=val Dot-separated path
rfl::Flatten<T> fields inlined No prefix needed
rfl::Rename<"x", T> --x=val Bypasses kebab conversion
rfl::Positional<T> bare token Matched in declaration order
rfl::Short<"x", T> -x value or -x=value Single-character alias

Architecture

Parsing proceeds in three stages:

  1. parse_argv — categorizes raw tokens into named, short_args, and positional buckets (ParsedArgs struct). No type information needed.
  2. resolve_args — uses compile-time metadata from the target struct to map short aliases to long names, reclaim values from bool short flags, and merge positional arguments. Produces a flat map<string, string>.
  3. Reader — implements reflect-cpp's IsReader concept by presenting virtual tree nodes over the flat map. Each node is a {map*, path} pair — no data copying, just prefix-based lookup via lower_bound.

Files

  • include/rfl/cli/read.hpp — public API
  • include/rfl/cli/Reader.hpp — Reader + parse_value overloads
  • include/rfl/cli/Parser.hpp — Parser type alias
  • include/rfl/cli/parse_argv.hppargv -> ParsedArgs
  • include/rfl/cli/resolve_args.hppParsedArgs -> map<string, string>
  • include/rfl/cli.hpp — aggregator header
  • include/rfl/SnakeCaseToKebabCase.hpp — processor
  • include/rfl/Positional.hppPositional<T> wrapper
  • include/rfl/Short.hppShort<"x", T> wrapper