Home

Awesome

scelta

C++17 zero-overhead syntactic sugar for variant and optional.

build stability license gratipay conan badge.cpp on-wandbox on-godbolt

Table of Contents

Overview

std::variant and std::optional were introduced to C++17's Standard Library. They are sum types that can greatly improve type safety and performance.

However, there are some problems with them:

scelta aims to fix all the aformenetioned problems by providing zero-overhead syntactic sugar that:

Implementation independent

scelta detects and works out-of-the-box with:

Other implementation can be easily adapted by providing specializations of the helper traits structs. PRs are welcome!

Curried visitation syntax

scelta provides curried, constexpr-friendly, and SFINAE-friendly visitation utilities both for variant and optional. The final user syntax resembles pattern matching. Recursive data structures are supported.

using shape = std::variant<circle, box>;

shape s0{circle{/*...*/}};
shape s1{box{/*...*/}};

// In place `match` visitation.
scelta::match([](circle, circle){ /* ... */ },
              [](circle, box)   { /* ... */ },
              [](box,    circle){ /* ... */ },
              [](box,    box)   { /* ... */ })(s0, s1);

The match function is intentionally curried in order to allow reuse of a particular visitor in a scope, even on different implementations of variant/optional.

using boost_optstr = boost::optional<std::string>;
using std_optstr = std::optional<std::string>;

// Curried `match` usage.
auto print = scelta::match([](std::string s)    { cout << s;       },
                           [](scelta::nullopt_t){ cout << "empty"; });

boost_optstr s0{/*...*/};
std_optstr s1{/*...*/};

// Implementation-independent visitation.
print(s0);
print(s1);

Recursive ADTs creation and visitation

Recursive variant and optional data structures can be easily created through the use of placeholders.

namespace impl
{
    namespace sr = scelta::recursive;

    // `placeholder` and `builder` can be used to define recursive
    // sum types.
    using _ = sr::placeholder;
    using builder = sr::builder<std::variant<int, std::vector<_>>>;

    // `type` evaluates to the final recursive data structure type.
    using type = sr::type<builder>;

    // `resolve` completely evaluates one of the alternatives.
    // (In this case, even the `Allocator` template parameter is
    // resolved!)
    using vector_type = sr::resolve<builder, std::vector<_>>;
}

using int_tree = impl::type;
using int_tree_vector = impl::vector_type;

After defining recursive structures, in place recursive visitation is also possible. scelta provides two ways of performing recursive visitation:

int_tree t0{/*...*/};

scelta::match(
    // Base case.
    [](int x){ cout << x; }
)(
    // Recursive case.
    [](auto recurse, int_tree_vector v){ for(auto x : v) recurse(v); }
)(t0);

// ... or ...

scelta::recursive::match<return_type>(
    // Base case.
    [](auto, int x){ cout << x; },

    // Recursive case.
    [](auto recurse, int_tree_vector v){ for(auto x : v) recurse(v); }
)(t0);

Monadic optional operations

scelta provides various monadic operations that work on any supported optional type. Here's an example inspired by Simon Brand's "Functional exceptionless error-handling with optional and expected" article:

optional<image_view> crop_to_cat(image_view);
optional<image_view> add_bow_tie(image_view);
optional<image_view> make_eyes_sparkle(image_view);
image_view make_smaller(image_view);
image_view add_rainbow(image_view);

optional<image_view> get_cute_cat(image_view img)
{
    using namespace scelta::infix;
    return crop_to_cat(img)
         | and_then(add_bow_tie)
         | and_then(make_eyes_sparkle)
         | map(make_smaller)
         | map(add_rainbow);
}

Installation/usage

Quick start

scelta is an header-only library. It is sufficient to include it.

// main.cpp
#include <scelta.hpp>

int main() { return 0; }
g++ -std=c++1z main.cpp -Isome_path/scelta/include

Running tests and examples

Tests can be easily built and run using CMake.

git clone https://github.com/SuperV1234/scelta && cd scelta
./init-repository.sh # get `vrm_cmake` dependency
mkdir build && cd build

cmake ..
make check # build and run tests

make example_error_handling # error handling via pattern matching
make example_expression     # recursive expression evaluation
make example_optional_cat   # monadic optional operations

All tests currently pass on Arch Linux x64 with:

Integration with existing project

  1. Add this repository and SuperV1234/vrm_cmake as submodules of your project, in subfolders inside your_project/extlibs/:

    git submodule add   https://github.com/SuperV1234/vrm_cmake.git   your_project/extlibs/vrm_cmake
    git submodule add   https://github.com/SuperV1234/scelta.git      your_project/extlibs/scelta
    
  2. Include vrm_cmake in your project's CMakeLists.txt and look for the scelta extlib:

    # Include `vrm_cmake`:
    list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/extlibs/vrm_cmake/cmake/")
    include(vrm_cmake)
    
    # Find `scelta`:
    vrm_cmake_find_extlib(scelta)
    

Documentation

scelta::nonrecursive::visit

Executes non-recursive visitation.

scelta::nonrecursive::match

Executes non-recursive in-place visitation.

scelta::recursive::builder

Allows placeholder-based definition of recursive ADTs.

scelta::recursive::visit

Executes recursive visitation.

scelta::recursive::match

Executes recursive visitation.

scelta::match

Executes visitation (both non-recursive and recursive). Attempts to deduce the return type from the base cases, optionally supports user-provided explicit return type.

Monadic optional operations

scelta provides various monadic optional operations. They can be used in two different ways:

optional<int> o{/* ... */};

// Free function syntax:
scelta::map(o, [](int x){ return x + 1; });

// Infix syntax:
o | scelta::infix::map([](int x){ return x + 1; });

These are the available operations:

The example file example/optional_cat.cpp shows usage of map and and_then using scelta::infix syntax.

Resources