How I use `variant` in the Leaf compiler
Tags: coding, cpp, leaf, programming
Published: 2018-05-25 05:24:56

---
title: How I use `variant` in the Leaf compiler
published: true
description: Practical use of the C++ variant type
tags: coding, cpp, leaf, programming
canonical_url: https://mortoray.com/2018/05/25/how-i-use-variant-in-the-leaf-compiler/
cover_image: https://mortoray.files.wordpress.com/2018/05/spices-2548653_640.jpg
---

Variants are data types that can store different types of values in them, as opposed to one fixed type. In contrast to a generic `object` or an `untyped` variable, there is a limited number of storable types. I use these in my [Leaf](http://leaflang.org/) compiler in two key places: logging, and type information.

> I'm still using boost::variant as opposed to std::variant. I'm waiting for widespread deployment of C++17 compilers before upgrading (such as with new long-term Ubuntu releases).


## Logging

I recently added a new logging system to Leaf. The caller provides an error reference and contextual data when making a log entry. `variant` is used to capture different types of contextual information. I previously used `variant` this way in my [low latency logging system](https://mortoray.com/2014/05/29/wait-free-queueing-and-ultra-low-latency-logging/).

```cpp
logger.error( "cerr-unknown-identifier", logger::item_symbol( symbol.name ) );
```

`cerr-unknown-identifier` is an error message identifier. It's a key to an error message stored in a YAML file, this particular error is:

```yaml
cerr-unknown-identifier:
    text: Unknown identifier `{symbol}`
```

Note the placeholder `{symbol}` there. Somehow the logger needs to replace that with a symbol. That's what the `logger::item_symbol( symbol.name )` argument provides.

But what about other types? For example, an argument mismatch call involves more information:

```yaml
mismatch-argcount:
    text: The function `{symbol}` expects {expect} argument(s), you provided {actual}.
```

The caller passes more arguments to `error` than before.

```cpp
logger.error( 'mismatch-argcount', logger::item_symbol( symbol.name ), 
    logger::item_expect( func_arg_count ), logger::item_actual( call_args.size() ) );
```

Different items carry different types of information. The function calls mask item construction, but each of these `item_` functions returns a `logger::item`.

```cpp
typedef boost::variant<
        std::string,
        int64_t,
        source_location> item_variant_t;
struct item {
    item_type_t type;
    item_variant_t value;
};
```

The `type` carries the semantic meaning, such as `i_symbol` or `i_actual`. The `value` contains the data associated with this item. It's a `variant` type that allows either a `string`, an `int64_t`, or a `source_location`.

> The `error` function has a few variations accepting lists of items and individual items. A macro is most often used to make these calls as it adds items for line and source information.


## Type Traits

Leaf's type system has two parallel systems: concrete types and type specifiers. Type specifiers allow incomplete types and type constraints. We see the specifiers in leaf source code.

```leaf
var pine : optional integer 32bit
var cone : optional = 25
var twig : float high = 1/2
```

`optional integer 32bit` contains three parts, stored in a `type_spec::part` structure. The `type_spec` doesn't know much about what these parts mean, only how to store them. It has a `std::vector<part>` list of parts.

As the value type of each part varies significantly, I use a variant to store all the possibilities.

```cpp
struct part {
    part_type_t type;
    boost::variant<
        bool,
        extr_type,
        extr_type::reference_t,
        int,
        intr_type_compat::type_t,
        intr_type::fun_class_t,
        intr_type_function::access_t,
        intr_type_tuple::pack_t,
        shared_ptr<intr_type const>, //MUST not be an instance!
        std::string,
        type_spec_symbol,
        intr_type_function::convention_t
    > value;
    //sub-parametrics
    std::vector<shared_ptr<type_spec const>> sub;
    //attached expression
    shared_ptr<node const> node_expr;
    shared_ptr<expression const> expr;
};
```

Because working with a `variant` can be burdensome, and also wanting stronger typing, users of `type_spec` don't use the variant directly. All access goes through template functions.

```cpp
// creating the `optional integer 32bit` specifier
type_spec ts.
ts.set<pt_data_bitsize>( 32 );
ts.set<pt_optional>( true );
ts.set<pt_fun_class>( intr_type::integer );
```

The `set` call prevents associating the wrong type of information with the part. It has this signature:

```cpp
template<part_type_t PT>
part & set( typename part_type_descriptor<PT>::type value )
```

I'm mapping an enum value to type information with specialized templates. The setup is hidden behind macros, but here's the `pt_optional` one for example:

```cpp
template<> struct part_type_descriptor<pt_optional> {
    typedef bool type;
};
```

There's a matching `if_get` function, which returns the value of a particular part if it exists. This approach adds strong-typing and hides the complexity of working with `variant` inside the `type_spec` class.

```cpp
template<part_type_t PT>
boost::optional<typename part_type_descriptor<PT>::type> if_get() const {
```

> I like C++'s ability to map an enum value into a concrete type. It, along with variant, is the key to making `type_spec` type-safe while holding variable types of information. This template flexibility is one of the things that attracts me to C++.


## What about you?

Let me know how you use `variant` in your code? Even if it's not C++, the concept exists in other languages. Alternatively, tell me about how you'd like to use `variant` but have only a generic object type available.