# Lyra: a header-only C++11 command line parser built from composable pieces

> Lyra is a Boost Software License 1.0 header-only argument parser for C++ 11 and later, where each option or argument is an independent parser combined with the pipe operator. It avoids exceptions in favour of result types, and it can bind directly to variables rather than to a dictionary.

**bfgroup/Lyra** — A simple to use, composable, command line parser for C++ 11 and beyond

- Repository: https://github.com/bfgroup/Lyra
- Website: https://bfgroup.github.io/Lyra/
- Stars: 605 · Forks: 69
- Language: C++
- License: BSL-1.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/bfgroup-lyra

## Every option in Lyra is a parser you can pipe into another

Lyra describes itself as a simple to use, composing, header only command line arguments parser for C++ 11 and beyond, and the composing part is the actual idea. Each opt or arg is described as an independent parser, and these are combined to produce a composite parser, which the README says can be done in stages across multiple function calls, or even projects. The combination operator is the pipe character, so a command line interface reads like a pipeline of small pieces rather than a long constructor call with a dozen parameters. The repository backs this up with an examples directory that includes combined_parser.cpp, doc_groups.cpp, doc_commands.cpp, into_vector.cpp, option_styles.cpp and option_termination.cpp, so the shape of the API is meant to be read from runnable code. The header-only nature matters for adoption too. The feature list states that Lyra has no external dependencies except the standard library, which means dropping it into a build does not pull in a package manager, a submodule, or a compiled library you have to ship.

## One declaration gives you parsing, conversion and usage text

The README's headline claim is that you define your interface once to get parsing, type conversions and usage strings with no redundancy. A parser for a single option is created like this.

```cpp
int width = 0;
auto cli = lyra::cli()
    | lyra::opt( width, "width" )
        ["-w"]["--width"]
        ("How wide should it be?");
```

Reading it from the inside out, the option binds to the variable width, the square bracket groups supply the short and long names, and the parentheses supply the description. The README states that the first argument to an opt is any variable, local or global member, of any type that can be converted from a string using std::ostream, and that types are deduced from bound variables or lambdas with conversion performed via operator<< behind the scenes. The description string is not decorative, since the usage string is built from the information supplied and a usage string can be obtained by inserting the parser into a stream. Lyra also binds parsers to lambdas when you want custom handling, and it can bind parsers to vectors for arguments that take multiple values.

## Errors come back as result values, so parse never throws at you

Lyra uses result types for error propagation rather than exceptions, and the README repeats the point after the first parse example.

```cpp
auto result = cli.parse( { argc, argv } );
if ( !result )
{
	std::cerr << "Error in command line: " << result.message() << std::endl;
	exit(1);
}

// Everything was ok, width will have a value if supplied on command line.
```

The parse call returns something you test for truth, and a failure carries a message you print before exiting. There is no try block and no catch, which is convenient in codebases that treat exceptions as a poor fit for argument handling, and it also means the compiler can see the failure path in the source. There is a caveat recorded in the feature list itself, and it is a real one for a library selling itself as header-only and dependency-free: it does not yet build with exceptions disabled, but that is stated as coming later. So a project that compiles with -fno-exceptions is not currently a supported configuration, even though the library's own error strategy would suit it. The feature list also credits Lyra with modelling POSIX standards for short and long option behaviour and with customizable option syntax, which is how you would support a Windows style or a custom prefix without writing a second parser.

## The pipe operator is what makes subcommands generic

Composing pays off most when a program has structure, and the feature list names recursive argument groups with a callback for detection, which the README says allows for easy and generic sub-command specifications. The shape is a group parser that can be nested inside another group, with a callback that fires when the group is matched, so a verb such as build or test can carry its own options without those options leaking into the top level. The examples directory reflects that, with doc_groups.cpp and doc_commands.cpp covering exactly this ground. The rest of the composition story is smaller pieces: opt specifies options that start with a short dash or long dash, and it may be argument taking, in which case it takes a second argument which is a hint, or it may be a pure flag, in which case it has only one argument which must be a boolean. arg specifies arguments that are not tied to options and so have no square bracket names, and otherwise works like opt, with the cardinality of args specifiable from one to many and option values limited to a specified set of values. The standard help options, -h, --help and -?, are available through a help parser that just takes a boolean to bind to.

## The single-header build exists, and the README tells you not to use it

There is a single-header version generated from the source files, found at data/single_include/lyra/lyra.hpp, and it comes with a list of caveats attached. It is not the preferred method of using the library. It is not thoroughly tested. It is intended for use cases like Compiler Explorer, where it allows for immediate experimentation. Support for it will not be a priority. The conclusion the README draws is to prefer the regular multi-file sources in your projects. That is a rare and welcome piece of candour, since a generated amalgamation is normally offered as a convenience and quietly becomes the version people paste into their builds. The repository layout supports both routes, with include/ holding the multi-file sources, tools/ holding the generation tooling, data/ holding the generated header, and CMakeLists.txt, meson.build and build.jam present for the various build systems, so if you want a package manager path the Conan Center entry exists, and if you vendor the sources you take the include/ tree.

## Supported standards, build systems and where the release cadence sits

The project declares support for C++ 11, 14, 17, 20 and 23, and the README badges mirror that list, which is a broad claim for a library that advertises itself as simple. The default branch is develop, and the continuous integration picture is spread across several services, with Azure DevOps pipelines and AppVeyor both configured and the README linking release and develop builds for each. Locally, the repository carries a .clang-format, a .clangd configuration and an .editorconfig, so formatting is handled by the tools rather than by convention, and CONTRIBUTING.adoc covers the contribution process. On releases, the numbered tags are Lyra 1.8.0 dated 2026-05-31, Lyra 1.7.0 dated 2025-08-07 and Lyra 1.6.1 dated 2022-11-07, which shows a project that ships when it has something to ship rather than on a fixed schedule. The last push to the repository was on 2026-07-29, so the code is being worked on, but the gap between the most recent release and that push is worth noticing if you are deciding whether to track a tag or follow the develop branch.

## Lyra against a hand-rolled parser and against a heavier CLI framework

There are two honest alternatives, and they fail in opposite directions. The first is to write the parsing yourself, which for a two-flag tool is faster to do than to depend on anything: read argv, loop, compare strings, assign. That works until the tool grows a third option, at which point you are maintaining a home-grown parser with your own error messages, your own help output, and your own bugs in the corner where an option appears without its value. Lyra's pitch is that the declaration is the documentation, and the generated usage text follows from the same declaration, which removes the class of bug where help output drifts from actual behaviour. The second alternative is a larger command line framework that also handles subcommand routing, configuration files, and shell completion generation. Lyra deliberately does less. It parses arguments and nothing else, has no external dependencies, and leaves configuration and completion to you. If your tool needs those, the framework is the right size. If your tool needs clean parsing with no build system consequences, Lyra is closer to the right size.

## Conclusion

Adopt Lyra when you are writing a C++ 11 or later command line tool and want parsing, type conversion and usage text derived from one declaration without adding a dependency, and skip it when you need exception-based error handling, since the library does not yet build with exceptions disabled and is not built around them. Verify first that the include path and your compiler standard line up, that your option value types stream out through operator<<, and that the single-header variant at data/single_include/lyra/lyra.hpp is not the version you want to depend on, since the README marks it as not thoroughly tested and not a priority for support.

## FAQ

### How do I add Lyra to a C++ project?

Lyra is header only with no external dependencies except the standard library, so you include it with `#include <lyra/lyra.hpp>` and add the sources to your build. The repository ships CMakeLists.txt, meson.build and build.jam, and the package is also listed in the Conan Center.

### Does Lyra report parse errors with exceptions?

No. Lyra uses result types for error propagation rather than exceptions, so parse returns a result you test and whose message() you can print. The README notes that the library does not yet build with exceptions disabled, with that support stated as coming later.

### How does Lyra handle subcommands and boolean flags?

Recursive argument groups with a callback for detection allow generic sub-command specifications. A pure flag is an opt with only one argument, which must be a boolean, while an option taking a value gets a second argument used as a hint, and args not tied to options have no bracket names.

## Sources

- [bfgroup/Lyra on GitHub](https://github.com/bfgroup/Lyra)
- [License: BSL-1.0](https://github.com/bfgroup/Lyra/blob/develop/LICENSE)
- [Project website](https://bfgroup.github.io/Lyra/)
- [README](https://github.com/bfgroup/Lyra/blob/develop/README.md)
- [Releases](https://github.com/bfgroup/Lyra/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bfgroup-lyra
