Home

Awesome


<p align="center"> <img src="docs/img/logo.png" width="300"> </p>

<a href=""><img src="docs/img/C++17.svg" alt="C++17"></a> <a href=""><img src="docs/img/include_option_hpp.svg" alt="#include <opt/option.hpp>"></a>

Replacement for std::optional with efficient memory usage and additional features.

Table of contents:

Overview

Use #include <opt/option.hpp> to include the library header file.

The contents of the library are available in the opt namespace.

Types with unused states.

opt::option<float> a = 123.456f;
// Has the same size as just float
static_assert(sizeof(a) == sizeof(float));

// Convert `opt::option<float>` into `opt::option<int>`, and print the value if it is not empty
a.map([](float x) { return int(x); }).inspect([](int x) {
    std::cout << x << '\n';
});
a = opt::none;
// Convert `opt::option<float>` to `opt::option<int>` if it contains a value; otherwise, invoke the 'else' condition.
int b = a.map_or_else([] { return 1; }, [](float x) { return int(x) + 5; });
std::cout << b << '\n';

int c = 2;
opt::option<int*> d = &c;
// Has the same size as just a pointer
static_assert(sizeof(d) == sizeof(int*));

// Print the dereferenced value
std::cout << **d << '\n';

// Empty opt::option is not nullptr for pointers!
d = nullptr;
// Print the pointer address or "empty option" if option does not contain one
std::cout << opt::io(d, "empty option") << '\n';

Complex types that contain unused states.

opt::option<std::tuple<int, unsigned, float>> a;
// Uses `float` value in `std::tuple` to store "has value" state
static_assert(sizeof(a) == sizeof(std::tuple<int, unsigned, float>));

a.emplace(1, 2u, 3.f);

std::cout << std::get<0>(*a) << std::get<1>(*a) << std::get<2>(*a) << '\n';

struct S1 {
    unsigned x;
    char y;
    bool z;
};
opt::option<S1> b{5u, 'a', false};
// Uses `bool` value in `S1` to store "has value" state
static_assert(sizeof(b) == sizeof(S1));

b.reset();
std::cout << b.has_value() << '\n';

struct S2 {
    S1 x;
    std::tuple<int, int> y;
};
opt::option<S2> c{S2{S1{1, 'b', true}, {2, 3}}};
// Uses `bool` value in `x` data member inside `S1` type to store "has value" state
static_assert(sizeof(c) == sizeof(S2));

c->x.x = 100u;
std::cout << c->x.x << '\n';

Nested opt::options.

opt::option<opt::option<bool>> a{true};
// Uses `bool` value to store two "has value" states
static_assert(sizeof(a) == sizeof(bool));

a->reset();
a.reset();

opt::option<opt::option<opt::option<opt::option<opt::option<float>>>>> b;
// Uses `bool` value to store emptiness level value
static_assert(sizeof(b) == sizeof(float));

(*****b).reset();
(****b).reset();
(***b).reset();
(**b).reset();
b->reset();
b.reset();

Why opt::option?

opt::option allows to minimize the type size to a minimum.

Minimizing the type size is always a good thing if used properly.
Cache locality can often improve performance of the program even more than any other performed optimization.

It supports reference types, so you can avoid using inconvenient std::reference_wrapper and dangerous nullable pointers.

Allows direct-list-initialization for an aggregate types and constructors without std::in_place (but they are still supported).

Features taken from Rust's std::option::Option (.take, .map_or(_else), .flatten, .unzip, etc.), monadic operations from C++23 (.and_then, .map (renamed .transform), .or_else) and custom ones (.ptr_or_null, opt::option_cast, opt::from_nullable, operators equivalent to methods, etc.).

Extended constexpr support for trivially move assignable types. Note that size optimizations prevent opt::option being constexpr-compatible.

Additional functionality

The option library provides extended functionality over standard std::optional, which can lead to the use of more efficient and cleaner code.

See feature list for a list of available features.

See reference for more details.

Compiler support

The library is tested with these compiler versions with sanitizers enabled1:

[!IMPORTANT] The library could work with other versions of compilers, but some functionality may be broken.

Consider using sanitizers when using library using an untested version of the compiler to prevent unexpected behaviour.

The library has various tests that tries to cover every part of the library.

Uses clang-tidy (18.1.8) to minimize the number of bugs and unexpected behavior.

CMake integration

The CMakeLists.txt file in the project root directory provides option INTERFACE target, which adds:

See project cmake variables for more information.

find_package

To use find_package command you need to firstly install the option project.

Generate the option project with the variable OPTION_INSTALL defined to TRUE:

cmake -B build -DOPTION_INSTALL=TRUE

Generate project into build directory, and allow to install it.

sudo cmake --install build

Install project from build directory. Requires administrative permissions

[!NOTE] The version file for the project has the COMPATIBILITY mode set to ExactVersion and ARCH_INDEPENDENT argument.

Use find_package to find the option library and target_link_libraries to specify dependency on it.

find_package(option REQUIRED)
...
target_link_libraries(<target> PRIVATE option)

Next, you can #include the library header to use it.

FetchContent

You can use FetchContent either to clone the git repository or specify the archive URL to directly download it.

Download through git repository:

include(FetchContent)
FetcnhContet_Declare(
    option
    GIT_REPOSITORY https://github.com/NUCLEAR-BOMB/option.git
    GIT_TAG        <commit/tag>
)
FetchContent_MakeAvailable(option)
...
target_link_libraries(<target> PRIVATE option)

[!TIP] You could specify the SYSTEM (since CMake 3.25) and EXCLUDE_FROM_ALL (since CMake 3.28) arguments to FetchContent_Declare but the library already uses target_include_directories with SYSTEM and it is header-only library.

Using URL to the archive:

include(FetchContent)
FetchContent_Declare(
    option
    URL https://github.com/NUCLEAR-BOMB/option/archive/<id>
    URL_HASH SHA256=<hash>
)
FetchContent_MakeAvailable(option)
...
target_link_libraries(<target> PRIVATE option)

You can create archive URL with Source code archive URLs.

<hash> is optional but recommended. With this integrity check you can more secure pin the library version and avoid possible data corruptions and "changed file in transit" scenarios.

[!TIP] Use FIND_PACKAGE_ARGS optional argument in FetchContent_Declare to make it firstly try a call to find_package.

ExternalProject

Download through git repository:

include(ExternalProject)
ExternalProject_Add(
    option
    PREFIX "${CMAKE_BINARY_DIR}/option"
    GIT_REPOSITORY https://github.com/NUCLEAR-BOMB/option.git
    GIT_TAG <commit/tag>
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ""
    INSTALL_COMMAND ""
    TEST_COMMAND ""
)
ExternalProject_Get_Property(option source_dir)

target_compile_features(<target> PRIVATE cxx_std_17)
target_include_directories(<target> SYSTEM PRIVATE "${source_dir}/include")

As it is a header-only library it doesn't requires building, configuring, installation or testing.

If you want to run test target before library is used:

include(ExternalProject)
ExternalProject_Add(
    option
    PREFIX "${CMAKE_BINARY_DIR}/option"
    GIT_REPOSITORY https://github.com/NUCLEAR-BOMB/option.git
    GIT_TAG <commit/tag>
    TIMEOUT 10
    INSTALL_COMMAND ""
    TEST_COMMAND ${CMAKE_COMMAND} --build . --target run-option-test
)

This will build the tests and run them with run-option-test target. Note that this may take some time to build tests.

[!NOTE] You can change the download method to download through URL instead of through git repository with URL and URL_HASH arguments.

add_subdirectory

You can directly embed the project and add it through CMake's add_subdirectory command.

add_subdirectory(<path>)

target_link_libraries(<target> PRIVATE option)

<path> is path to the root directory of the project (contains CMakeLists.txt).

How it works

The opt::option internally uses opt::option_traits which contains static methods that manipulates underlying value inside opt::option. That's provide a way to store an empty state in a opt::option without using additional bool flag variable.

opt::option_traits also defines recursively opt::option type optimization and allows nested opt::option to have same size as the contained value.

Quick list of built-in size optimizations:

See built-in traits for more information.

Compatibility with std::optional

The library is fully compatible with std::optional4, except:

You can replace std::optional with opt::option, taking into account that there are these exceptions.

About undefined behavior

The library actively uses platform-dependent behavior to exploit unused object states.

Recommended using sanitizers (AddressSanitizer and UndefinedBehaviorSanitizer) to catch unexpected behavior.

[!NOTE] The library doesn't break the strict aliasing rules. It uses std::memcpy to copy object bits instead of reinterpret_cast.

You can disable individual built-in traits to avoid using platform specific behavior for specific types. Or you can disable built-in traits entirely with a macro definition.

Build times

The opt::option is slightly slower to build than std::optional due to meta-programming overhead and addutional functionality.

The benchmarks are created with CMake's custom command which creates a file with 2000 instantiations of opt::option/std::optional using a Python script.

Benchmarks are performed on commit 0e882312c4451ac937103eb8830531ee2726f863.

Compiler (version, (stdlib), platform)opt::option <br/> Debugstd::optional <br/> Debugopt::option <br/> Releasestd::optional <br/> Release
MSVC (19.40.33811, x64)09:77406:14310:55206:100
Clang (18.1.8, libstdc++, x64)14:95812:75712:82911:140
Clang (18.1.8, libc++, x64)13:20308:41812:08507:168
GCC (14.2.0, libstdc++, x64)10:79310:96407:78307:849

[!NOTE] The Clang and GCC are used on WSL.

Time is in seconds (e.g. 123:456 is 123 seconds and 456 milliseconds).

On average, compiling opt::option takes ~1.33x longer than std::optional (on Debug configuration).

Examples

You can find examples in the examples/ directory.

The output of all the examples is checked using script at examples/check.py.

Footnotes

  1. When possible uses Address Sanitizers and Undefined Behavior Sanitizer. Note that some compilers/versions have unstable sanitizer support, so the CI tests are disables that options.

  2. Requires either boost.pfr or ptr library.

  3. Requires identifier __PRETTY_FUNCTION__ or compiler built-in __builtin_FUNCSIG().

  4. Including: conditionally enabled constructors and methods, propagating trivial constructors and operators, propagating deleted constructors, operators.