/knot

Primary LanguageC++MIT LicenseMIT

knot

knot is a cpp++17 dependency free header only library to provide generic utility functions such as hash_value(), serialize(), debug() (without member names), lexicographic order and comparison operators (superseded by spaceship operator in c++20) to aggregate types with no base classes. knot also supports most std containers (tuple, pair, optional, variant, most range containers). The original motivation was #[derive(Debug)] from rust and similar.

struct Point {
  int x = 0;
  int y = 0;

  KNOT_COMPAREABLE(Point) // Generates comparison ops
};

void example(const Point& p) {
  std::size_t hash = knot::hash_value(p);
  std::cout << knot::debug(p) << '\n';
  // Eg: "(1, 2)"

  // should serialize to 8 bytes
  std::vector<uint8_t> bytes = knot::serialize(p);
  assert(p == knot::deserialize<Point>(bytes.begin(), bytes.end()));
}

Many of the above operations are implemented in terms of more generic static traversals. For example if you consider std::pair<std::string, std::optional<int>> as depth 3 tree with pair at the root, string and optional as its children, and the strings characters and int as the leaves. knot::preorder(const T&) will statically visit this tree in a preorder traversal, likewise with knot::postorder(const T&). knot::visit(const T&) will visit the direct children, same as std::visit for variant, the members of a struct, or the elements of a range. All the traveral functions will statically skip visiting objects if the supplied visitor cannot be called, be wary of implicit conversions.

enum class Op {Add, Sub};

struct BinaryExpr;
struct UnaryExpr;

using Expr = std::variant<std::unique_ptr<BinaryExpr>, std::unique_ptr<UnaryExpr>, int>;

struct BinaryExpr {
  Op op;
  Expr lhs;
  Expr rhs;
};

struct UnaryExpr {
  Op op;
  Expr child;
};

void example(const Expr& expr) {
  std::size_t hash = knot::hash_value(expr);
  std::cout << knot::debug(expr) << '\n';

  std::vector<uint8_t> bytes = knot::serialize(expr);
  std::optional<Expr> deserialized = knot::deserialize<Expr>(bytes.begin(), bytes.end());

  // Don't have op== and unique_ptr doesn't do deep comparisons anyway so compare strings instead
  assert(deserialized.has_value() && knot::debug(expr) == knot::debug(*deserialized));
}

// Count the number of Add operators in the expression tree
int num_adds(const Expr& expr) {
  return knot::preorder_accumulate(expr, [](int acc, Op op) {
    return op == Op::Add ? acc + 1 : acc;
  }, 0);
}

// Print all leaf integers in the expression tree
void dump_leaf_values(const Expr& expr) {
  return knot::preorder(expr, [](int leaf) {
    std::cout << "Leaf: " << leaf << '\n';
  });
}

Look at the unit tests under test/ for more examples.