Skip to main content

Compiler Wrapper

This example shows two techniques that compilers and build tools commonly need: short-cluster flags (-vv, -O2) and negatable key-value options (-Werror, -Wno-unused, --feature=lto, --feature=no-pgo).

Source​

Source: examples/compiler.cc

examples/compiler.cc
#include <iostream>
#include <map>
#include <string>
#include <vector>

#include "cli/argument.hh"
#include "cli/parser.hh"

struct Args {
cli::Description description{
"Compiler-wrapper example: short-cluster flags, -W<warning>, "
"--feature=<name> with negatable enable/disable."};

// cli::Help<> is sugar for Arg<"help",'h', print_help|exit_success,
// nargs::none>
cli::Help<> help{{.help = "Show help and exit"}};

// -O0 .. -O3
cli::Arg<"optimize", 'O',
cli::conversion::integer<int> | cli::validation::range<0, 3> |
cli::pack::set_once>
optimize{{.help = "Optimisation level (0-3)"}};

// -v / -vv: counted verbosity
cli::Arg<"verbose", 'v', cli::pack::increment, cli::nargs::none> verbose{
{.help = "Increase verbosity (repeatable)"}};

// -Werror → warnings["error"] = true
// -Wno-unused → warnings["unused"] = false
cli::Arg<"warning", 'W',
cli::conversion::negatable<"no-"> | cli::pack::insert_or_assign>
warnings{{.help = "Enable/disable a warning (e.g. -Werror, -Wno-unused)"}};

// --feature=lto → features["lto"] = true
// --feature=no-pgo → features["pgo"] = false
cli::Arg<"feature", '\0',
cli::conversion::negatable<"no-"> | cli::pack::insert_or_assign>
features{{.help = "Enable/disable a feature (e.g. --feature=lto, "
"--feature=no-pgo)"}};

cli::Positional<std::string, cli::nargs::one_or_more> sources{
{.help = "Source files", .presence = cli::required}};
};

auto main(int argc, char* argv[]) -> int {
const auto args = cli::parse_or_exit<Args>(argc, argv);

if (args.optimize.value()) {
std::cout << "optimize: -O" << *args.optimize << '\n';
}

if (const std::size_t v = args.verbose.value(); v > 0) {
std::cout << "verbosity: " << v << '\n';
}

for (const auto& [name, enabled] : args.warnings.value()) {
std::cout << "warning: " << name << " = " << (enabled ? "true" : "false")
<< '\n';
}

for (const auto& [name, enabled] : args.features.value()) {
std::cout << "feature: " << name << " = " << (enabled ? "true" : "false")
<< '\n';
}

std::cout << "sources:";
for (const std::string& src : args.sources.value()) {
std::cout << ' ' << src;
}
std::cout << '\n';

return 0;
}

What This Example Shows​

Short-cluster flags — -vv is parsed as two -v occurrences, incrementing the verbose counter. -O2 passes 2 as the attached value to optimize.

conversion::negatable<"no-"> — converts a string token to std::pair<std::string, bool> by detecting the compile-time prefix:

InputOutput
"error"{"error", true}
"no-unused"{"unused", false}

pack::insert_or_assign — stores the pair into a std::map<std::string, bool>, so later occurrences of the same key overwrite earlier ones.

Combining them gives -Werror -Wno-unused → warnings = {{"error", true}, {"unused", false}}.

Try It​

cli20@cli20-dev: ~
cli20@cli20-dev:~$ compiler

Things to try:

  • Happy path — -O2 -vv -Werror -Wno-unused --feature=lto --feature=no-pgo main.cc
  • Cluster flags — -vvv main.cc to see verbosity count 3
  • Attached value — -O3 main.cc vs -O 3 main.cc (both work)
  • Override — -Werror -Wno-error main.cc to see the last value win (error = false)
  • Out-of-range level — -O5 main.cc to see range validation reject it
  • Help — -h or --help