繁体   English   中英

将 boost::recursive_variant 与 std::unordered_map 结合使用

[英]Using boost::recursive_variant with std::unordered_map

我正在尝试创建一个std::unordered_map ,其中boost::variant作为键,integer 作为值。 我正在努力使这项工作:

#include <boost/variant.hpp>

#include <unordered_map>
#include <string>

enum Token {};

struct AssignExpr;
struct LiteralExpr;

using Expr = boost::variant<boost::blank, 
                            boost::recursive_wrapper<AssignExpr>,
                            boost::recursive_wrapper<LiteralExpr>>;

using Literal = int;

struct LiteralExpr {
  Literal literal;

  explicit LiteralExpr(const Literal &literal);
};

struct AssignExpr {
  Token name;
  Expr value;

  AssignExpr(const Token &name, const Expr &val);
};

int main() {
  LiteralExpr li{0};
  AssignExpr ae{{}, li};

  std::unordered_map<Expr, std::size_t> m_locals{{li, 10}, {ae, 20}};
}

我尝试添加operator==和 hash function但出现链接错误。

如果您使用的是基于散列的容器,则所有元素类型都必须是可散列的并且具有相等性可比性。

Boost 变体根据 ADL 自定义点hash_value实现哈希 - 默认情况下使用boost::hash<T>{} 这意味着获得可哈希性的最简单方法是为所有类型实现hash_value - 包括boost::blank

接下来,让我们默认operator==

bool operator==(LiteralExpr const&) const = default;
bool operator==(AssignExpr const&) const = default;

或者,如果您的编译器足够时髦,可以在一个 go 中进行三向比较:

auto operator<=>(LiteralExpr const&) const = default;
auto operator<=>(AssignExpr const&) const = default;

然后它全部编译,并且更好地运行而不会引起 UB。 请确保您的 hash 同意平等关系!

现场演示

#include <boost/variant.hpp>
#include <boost/functional/hash.hpp>

#include <string>
#include <unordered_map>
#include <utility>

namespace boost {
    [[maybe_unused]] static inline size_t hash_value(boost::blank) { return 0; }
} // namespace boost

namespace MyLib {
    using boost::hash_value;

    enum Token {A,B,C,D,E,F,G,H};

    [[maybe_unused]] static constexpr auto name_of(Token tok) {
        return tok["ABCDEFGH"];
    }

    struct AssignExpr;
    struct LiteralExpr;

    using Expr = boost::variant<                //
        boost::blank,                           //
        boost::recursive_wrapper<AssignExpr>,   //
        boost::recursive_wrapper<LiteralExpr>>; //

    using Literal = int;

    struct LiteralExpr {
        Literal literal;

        explicit LiteralExpr(Literal literal) : literal(literal) {}

        auto operator<=>(LiteralExpr const&) const = default;
        friend size_t hash_value(LiteralExpr const& le) {
            return hash_value(le.literal);
        }

        friend std::ostream& operator<<(std::ostream& os, LiteralExpr const& le) {
            return os << le.literal;
        }
    };

    struct AssignExpr {
        Token name;
        Expr  value;

        AssignExpr(Token name, Expr val) : name(name), value(std::move(val)) {}

        auto operator<=>(AssignExpr const&) const = default;
        friend size_t hash_value(AssignExpr const& ae) {
            auto h = hash_value(ae.name);
            boost::hash_combine(h, hash_value(ae.value));
            return h;
        }

        friend std::ostream& operator<<(std::ostream& os, AssignExpr const& ae) {
            return os << name_of(ae.name) << " := " << ae.value;
        }
    };

} // namespace MyLib

#define FMT_DEPRECATED_OSTREAM
#include <fmt/ranges.h>
#include <fmt/ostream.h>
template <> struct fmt::formatter<MyLib::Expr> {
    auto parse(auto& ctx) const { return ctx.begin(); }
    auto format(MyLib::Expr const& e, auto& ctx) const {
        return boost::apply_visitor(
            [&](auto const& el) { return format_to(ctx.out(), "Expr({})", el); },
            e);
    }
};

int main() {
    using namespace MyLib;

    LiteralExpr li{42};
    AssignExpr  ae{D, li};

    std::unordered_map<Expr, std::size_t> m_locals{{li, 10}, {ae, 20}};

    fmt::print("{}\n", fmt::join(m_locals, "\n"));
}

印刷

(Expr(D := 42), 20)
(Expr(42), 10)

旁注

我会简化。 LiteralExpr不会在Literal上添加任何内容(当然也不会递归)。

除非您真的需要该功能(在您的 AST 中通常类似于Null ),否则请跳过blank

您所有的结构似乎都可以是聚合体,所以也许只是享受聚合体构造。

struct Nil {
    friend constexpr size_t hash_value(Nil) { return 0; }
    constexpr bool operator==(Nil) const { return true; }
};

using Literal = int;

struct AssignExpr;
using Expr = boost::variant<               //
    Nil,                                   //
    Literal,                               //
    boost::recursive_wrapper<AssignExpr>>; //

enum Token {A,B,C,D,E,F,G,H};

struct AssignExpr {
    Token name;
    Expr  value;
};

您可能会稍微同化相等和 hash 操作:

auto key() const { return std::tie(name, value); }

bool operator==(AssignExpr const& rhs) const { return key() == rhs.key(); }
friend size_t hash_value(AssignExpr const& ae) { return hash_value(ae.key()); }

这使得两者不能不同步。

现在它变成了Live Demo ,缩短了 25 行并且功能更强大:

(Expr(Nil), 30)
(Expr(D := 42), 20)
(Expr(42), 10)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM