C++ 等价于 Rust 枚举

Kin*_*407 9 c++ enums

这个例子展示了一种在 Rust 中处理不同类型消息的优雅方式。它有 4 个变体,一些变体具有子成员,只有在枚举属于该特定类型时才能访问这些子成员。在 TypeScript 中也可以使用类似的模式。

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
Run Code Online (Sandbox Code Playgroud)

在 C++ 中,这很可能与以下代码进行比较。

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;
};
Run Code Online (Sandbox Code Playgroud)

但是,这种方式不是类型安全的,并且内存管理会变得混乱(例如,Message如果MessageTypeis被破坏,则确保字符串被释放WRITE)。在现代 C++ 中执行此操作的最佳方法是什么?

0x5*_*453 17

std::variant就是为了。

(但请注意,由于 C++ 没有模式匹配,因此与 Rust 等语言相比,使用变体仍然相当麻烦。)

使用std::variant,您的示例将如下所示

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>;
Run Code Online (Sandbox Code Playgroud)

最接近 Rustmatch表达式的是std::visit. 访问此示例变体可能如下所示:

// 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
Run Code Online (Sandbox Code Playgroud)


小智 5

受到0x5453答案的启发,我编写了一个片段来帮助 Rust 风格的处理std::variant

// https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

template <typename Val, typename... Ts>
auto match(Val val, Ts... ts) {
    return std::visit(overloaded{ts...}, val);
}
Run Code Online (Sandbox Code Playgroud)

用法:

match(value, [](Type1& type1) {
        ;
    },
    [](Type2& type2) {
        ;
    },
    ...
    [](TypeN& typeN) {
        ;
    }
);
Run Code Online (Sandbox Code Playgroud)

例子:

#include <iostream>
#include <variant>
#include <string>

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

template <typename Val, typename... Ts>
auto match(Val val, Ts... ts) {
    return std::visit(overloaded{ts...}, val);
}

class A {
public:
    A() : v_(0) {}
    A(int v) : v_(v) {
        std::cout << "Initializing " << V() << std::endl;
    }
    A(const A& a) : v_(a.v_) {
        std::cout << "Copying " << V() << std::endl; }
    A(A&& a) : v_(a.v_) {
        std::cout << "Moving " << V() << std::endl;
        a.Drop();
    }
    // Explicitly delete it just in case.
    A& operator=(const A&) = delete;
    A& operator=(A&& a) {
        v_ = a.v_;
        a.Drop();
        std::cout << "Move assigning " << V() << std::endl;
        return *this;
    }
    ~A() {
        if (V()) {
            std::cout << "Deconstructing " << V() << std::endl;
        }
    }
    int V() { return v_; }
private:
    void Drop() {
        v_ = 0;
    }
    int v_;
};
auto func(bool choice) -> std::variant<A, int> {
    if (choice) {
        // Initializing 1
        return A(1);
        // Moving 1
    } else {
        return 233;
    }
};
auto main() -> int {
    A a;
    std::cout << match(func(true),
        [&](A& ret) -> std::string {
            // Move assigning 1
            a = std::move(ret);
            return "A";
        },
        [&](int ret) -> std::string {
            a = A(ret);
            return "int";
        }
    ) << std::endl; // A
    // 1
    std::cout << a.V() << std::endl;

    return 0;
    // Deconstructing 1
}
Run Code Online (Sandbox Code Playgroud)

输出:

Initializing 1
Moving 1
Move assigning 1
A
1
Deconstructing 1
Run Code Online (Sandbox Code Playgroud)

参考:

https://en.cppreference.com/w/cpp/utility/variant/visit

https://polomack.eu/std-variant/