Replicating Swift's Enumeration Associated Values in C++
Tags: swift, cpp, programming, tutorial
Published: 2022-12-21 21:44:52

Lately I'm working on a Metal engine as a hobby project.
I work with Metal in my day job and it has become my favourite  graphics API in terms of developer experience.
At home I wanted the full *"Apple Experience"* so I decided to use Swift instead of the usual suspects (C/C++, Rust...).

I've never worked with Swift before so it's all new. Coming from C++ I feel a bit constrained from time to time (I still heavily dislike the way structs and classes work), but so far I'd describe it as a *"comfy"* language. It's easy to work with, the syntax is simple (most of the time), the tooling is pretty good, and it has **a lot** of syntax sugar.

One example of syntax sugar that I love is they way you can add arbitrary values to enums, officially called **Associated Values**.
I love it because it allows me to simplify APIs and reduce boiler plate a lot.

Let's see a simplified example from my project:

```swift
let PI: Float = 3.1416
let TAU = 2 * PI

struct Vec3 { let x, y, z : Float } // Imagine there's vector math here

enum Axis
{
    case X, Y, Z
    case CUSTOM(Vec3)
}

struct Transform
{
    // ...
    func rotate_around(axis: Axis, radians: Float)
    {
        var the_axis: Vec3
        switch axis
        {
            case .X: the_axis = Vec3(x:1, y:0, z:0)
            case .Y: the_axis = Vec3(x:0, y:0, z:0)
            case .Z: the_axis = Vec3(x:0, y:0, z:1)
            case .CUSTOM(let a): the_axis = a
        }

        print("Rotating \(radians) radians around the(\(the_axis.x), \(the_axis.y). \(the_axis.z)) axis")
        // Do math stuff with the axis
    }
    // ...
}

let t = Transform(/* ctr arguments*/)

t.rotate_around(axis: .X, radians: 0.25 * TAU)
t.rotate_around(axis: .Y, radians: 0.5  * TAU)
t.rotate_around(axis: .Z, radians: 0.75 * TAU)

let my_axis = Vec3(x: 0, y: 0.3333, z: 0.6667)
t.rotate_around(axis: .CUSTOM(my_axis), radians: TAU)
```

The output of which is:
> Rotating 1.5708 radians around the(1.0, 0.0. 0.0) axis
> Rotating 3.1416 radians around the(0.0, 0.0. 0.0) axis
> Rotating 4.7124 radians around the(0.0, 0.0. 1.0) axis
> Rotating 6.2832 radians around the(0.0, 0.3333. 0.6667) axis

Look at how nice and simple the implementation of `rotate_around` is! ❤️

It was just a matter of time until I encountered something at work that could use a feature like this. And that moment came today.
At work we use C++, and enums there are not this complex, so APIs like this can get a lot of boilerplate to get them working.
But thankfully, this line in [Swift's docs](https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html) made me realise there's a way to get a similar thing working on C++:

> Enumerations similar to these are known as discriminated unions, tagged unions, or variants in other programming languages.

*Variants*!
So I went on and drafted a quick C++ alternative to my Swift code:

```c++
#include <iostream>
#include <variant>

constexpr float PI = 3.1416f;
constexpr float TAU = 2 * PI;

struct Vec3 { float x, y, z; }; // Imagine there's vector math here

enum class PredefinedAxis
{
    X, Y, Z
};
using CustomAxis = Vec3;
using Axis = std::variant<PredefinedAxis, CustomAxis>;

struct Transform
{
    // ...
    void rotate_around_axis(Axis axis, float radians)
    {
        Vec3 the_axis;
        if (auto* predefined = std::get_if<PredefinedAxis>(&axis))
        {
            switch (*predefined)
            {
                case PredefinedAxis::X: the_axis = Vec3{.x=1, .y=0, .z=0}; break;
                case PredefinedAxis::Y: the_axis = Vec3{.x=0, .y=1, .z=0}; break;
                case PredefinedAxis::Z: the_axis = Vec3{.x=0, .y=0, .z=1}; break;
            }
        }
        else if (auto* custom = std::get_if<CustomAxis>(&axis))
        {
            the_axis = *custom;
        }
        else
        {
            // We shouldn't get here!!
        }

        std::cout << "Rotating " << radians << " radians around the (" << the_axis.x << ", " << the_axis.y << ", " << the_axis.z << ") axis" << std::endl;
        // Do math stuff with the axis
    }
    // ...
};

auto main() -> int
{
    auto t = Transform{/* ctr arguments */};

    t.rotate_around_axis(PredefinedAxis::X, 0.25 * TAU);
    t.rotate_around_axis(PredefinedAxis::Y, 0.5  * TAU);
    t.rotate_around_axis(PredefinedAxis::Z, 0.75 * TAU);

    auto my_axis = Vec3{.x = 0, .y = 0.3333, .z = 0.6667};
    t.rotate_around_axis(my_axis, TAU);
}
```

Which outputs:
> Rotating 1.5708 radians around the (1, 0, 0) axis
> Rotating 3.1416 radians around the (0, 1, 0) axis
> Rotating 4.7124 radians around the (0, 0, 1) axis
> Rotating 6.2832 radians around the (0, 0.3333, 0.6667) axis

Of course, this being C++ it's more verbose and it feels a bit *hacky* (especially the `using` part), but it works!
The implementation of `rotate_around_axis` is messier, but I actually like the *use* of it way better!

I look forward to learning more cool features from Swift that I can copy in C++!