C++ equivalent of Rust enums

Viewed 5008

This example shows an elegant way to deal with messages of different types in Rust. It has 4 variants and some variants have sub members that are only accessible if the enum is of that specific type. A similar pattern is also possible in TypeScript.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

In C++ this would most likely compare to the following code.

struct Message
{
    enum MessageType {QUIT, MOVE, WRITE, CHANGECOLOR} type;
    union MessageContent
    {
        struct Move { int x; int y;} move;
        std::string write;
        std::tuple<int, int, int> changeColor;
    } content;
};

However, this way isn't typesafe and memory management is going to become messy (e.g. making sure that string gets released if Message gets destructed if MessageType is WRITE). What is the best way to do this in modern C++?

1 Answers

This is what std::variant is for.

(Though note, since C++ does not have pattern matching, using variants is still rather cumbersome compared to languages like Rust.)

Using std::variant, your example would look like this:

struct Quit {};
struct Move { int32_t x; int32_t y; };
struct Write { std::string s; };
struct ChangeColor { int32_t r; int32_t g; int32_t b; };
using Message = std::variant<Quit, Move, Write, ChangeColor>;

The closest thing to Rust's match expressions is std::visit. Visiting this example variant could look like:

// Utility to allow overloading lambdas for use in std::visit
template<class... Ts>
struct overload : Ts... {
    using Ts::operator()...;
};
template<class... Ts>
overload(Ts...) -> overload<Ts...>;

int main() {
    auto visitor = overload{
        [](const Quit& q)        { std::cout << "Quit\n"; },
        [](const Move& m)        { std::cout << "Move " << m.x << " " << m.y << "\n"; },
        [](const Write& w)       { std::cout << "Write " << w.s << "\n"; },
        [](const ChangeColor& c) { std::cout << "ChangeColor " << c.r << " " << c.g << " " << c.b << "\n"; }
    };

    Message m1{Quit{}};
    Message m2{Move{1, 2}};
    Message m3{Write{"a"}};
    Message m4{ChangeColor{1, 2, 3}};
    std::visit(visitor, m1);
    std::visit(visitor, m2);
    std::visit(visitor, m3);
    std::visit(visitor, m4);
}
// This prints:
//   Quit
//   Move 1 2
//   Write a
//   ChangeColor 1 2 3
Related