相当于C++的存在量化

Equivalent of existential quantification in C++?

本文关键字:量化 存在 C++ 相当于      更新时间:2023-10-16

为了帮助自学C++,我正在研究一个红黑树的实现。 来自哈斯克尔,我,认为看看我是否可以强制执行一个属性会很有趣红黑树静态地在C++的类型系统中:

  1. 节点为红色或黑色。
  2. 根是黑色的[...]
  3. 所有叶子(无(都是黑色的。
  4. 如果节点为红色,则其两个子节点均为黑色。
  5. 从给定节点到其任何后代 NIL 节点的每条路径都包含 相同数量的黑色节点。[...]

我想出了如何为树的节点制作类型以满足约束 1,使用模板的 3、4 和 5:

template <typename Key, typename Value>
class RedBlackTree {
private:
  enum class color { Black, Red };
  // [1. A node is either red or black]
  template <color Color, size_t Height>
  struct Node {};
  // [3. All leaves are black]
  struct Leaf : public Node<color::Black, 0> {};
  template <color Left, color Right, size_t ChildHeight>
  struct Branch {
  public:
    template <color ChildColor>
    using Child = unique_ptr<Node<ChildColor, ChildHeight>>;
    Key key;
    Value value;
    Child<Left> left;
    Child<Right> right;
    Branch(Key&& key, Value&& value, Child<Left> left, Child<Right> right) :
      key { key }, value { value }, left { left }, right { right } {}
  };
  // [4. If a node is red, then both its children are black.]
  // [5. Every path from a given node to any of its descendant NIL nodes contains 
  //     the same number of black nodes.]
  template <size_t Height>
  struct RedBranch: public Node<color::Red, Height>
                  , public Branch<color::Black, color::Black, Height> {
  public:
    using RedBlackTree::Branch;
  };
  // [5. Every path from a given node to any of its descendant NIL nodes contains 
  //     the same number of black nodes.]
  template <size_t Height, color Left, color Right>
  struct BlackBranch: public Node<color::Black, Height>
                    , public Branch<Left, Right, Height-1> {
  public:
    using RedBlackTree::Branch;
  };
  // ...
};

我遇到障碍的地方是给出将存储在RedBlackTree中的root指针实例 满足属性 2 但仍有用的类型。

我想要的是这样的:

template <typename Key, typename Value>
class RedBlackTree {
  //...
  unique_ptr<Node<color::Black,?>> root = std::make_unique<Leaf>();
  //...
}

(借用 Java 的语法(,所以我可以在树的高度上添加通配符。 这个当然,不起作用。

如果我这样做,我可以让我的代码编译

template <typename Key, typename Value, size_t TreeHeight>
class RedBlackTree {
  //...
  unique_ptr<Node<color::Black,TreeHeight>> root = std::make_unique<Leaf>();
  //...
}

但这不是我想要的树的类型 - 我不想要树本身的类型以反映其高度,否则当我插入键值对。我希望能够更新我的root以包含指向黑色的指针 Node任何高度。

回到哈斯克尔兰,我会用存在主义解决这个问题量化

data Color = Black | Red
data Node (color :: Color) (height :: Nat) key value where
  Leaf :: Node 'Black 0 key value
  BlackBranch :: Branch left right height key value -> Node 'Black (height+1) key value
  RedBranch :: Branch 'Black 'Black height key value -> Node 'Red height key value
data Branch (left :: Color) (right :: Color) (childHeight :: Nat) key value = Branch
  { left  :: Node left childHeight key value
  , right :: Node right childHeight key value
  , key   :: key
  , value :: value
  }
data RedBlackTree key value where
  RedBlackTree :: { root :: Node 'Black height key value } -> RedBlackTree key value

在 C++14(或者可能是 C++17(中是否有等效的概念,或者我可以编写我的struct定义以能够为root提供有用且正确的类型的替代方法?

template<class K, class T>
struct NodeView {
  virtual NodeView const* left() const = 0;
  virtual NodeView const* right() const = 0;
  virtual K const& key() const = 0;
  virtual T const& value() const = 0;
private:
  ~NodeView() {} // no deleting it!
};

这是一个接口。

让树节点实现此接口。 允许并鼓励他们在一方或另一方没有孩子的情况下返回nullptr

在基础结构中,将根节点作为模板参数。 使用模板愚蠢检查它是否为黑色。

使用make_shared将其存储在std::shared_ptr

auto tree = std::make_shared<std::decay_t<decltype(tree)>>(decltype(tree)(tree));
std::shared_ptr<NodeView const> m_tree = std::move(tree);

其中m_tree成员是根管理结构的成员。

您现在对泛型树具有只读访问权限。 在编译时,它由存储它的代码保证为平衡的红黑树。 在运行时,它只是保证是一棵树

您可以将更多的保证信息泄漏到我上面写的界面中,但这会使界面混乱,超出读者通常需要知道的范围。 (例如,具有不同的RedBlack接口节点类型(。

现在,如果一个短函数的主体太多而无法信任,并且您宁愿信任一堵模板代码墙,我们可以这样做:

template<template<class...>class Test, class T>
struct restricted_shared_ptr {
  template<class U,
    std::enable_if_t< Test<U>{}, int> = 0
  >
  restricted_shared_ptr( std::shared_ptr<U> pin ):ptr(std::move(pin)) {}
  restricted_shared_ptr(restricted_shared_ptr const&)=default;
  restricted_shared_ptr(restricted_shared_ptr &&)=default;
  restricted_shared_ptr& operator=(restricted_shared_ptr const&)=default;
  restricted_shared_ptr& operator=(restricted_shared_ptr &&)=default;
  restricted_shared_ptr() = default;
  T* get() const { return ptr.get(); }
  explicit operator bool() const { return (bool)ptr; }
  T& operator*() const { return *ptr.get(); }
  T* operator->() const { return ptr.get(); }
private:
  std::shared_ptr<T> ptr;
};

现在我们只写一个任意的模板检查,上面写着"这足以让我满意"。

并存储restricted_shared_ptr< MyCheck, NodeView<K,T> const >. 无法在此共享指针中存储 a 类型,该类型不会在没有未定义行为的情况下传递MyCheck

在这里,你需要信任MyCheck的构造函数来做它所说的。

template<class T>
struct IsBlackNode:std::false_type{};
template<class K, class V, std::size_t Height, class Left, class Right>
struct IsBlackNode< BlackNode<K, V, Height, Left, Right> >:std::true_type{};

这是只有BlackNode才能通过的要求。

因此,restricted_shared_ptr< IsBlackNode, NodeView<K, T> const >是指向通过IsBlackNode测试实现NodeView<K,T>接口的事物的共享指针。

注意

Yakk 的回答更惯用C++——这个答案展示了如何编写(或至少开始编写(与 Haskell 版本更相似的东西。

当您看到模拟 Haskell 需要多少C++时,您可以选择改用本地习语。

博士

大多数 Haskell 不变量(和属性(根本不是在类型中静态表达的,而是在各种构造函数的(运行时(代码中表达的。类型系统通过保证其中一个构造函数确实运行,并跟踪它是用于模式匹配调度的构造函数来提供帮助。


IIUC,您的 Haskell Node 类型没有四个类型参数,而是两个:

data Node (color :: Color) (height :: Nat) key value where

修复颜色和高度的类型 - 仅未确定键和值类型。所有四个记录都是记录,但其中两个记录具有固定类型。

因此,最接近的简单翻译是

template <typename Key, typename Value>
struct Node {
    Color color_;
    size_t height_;
    Key key_;
    Value val_;
};

棘手的部分是没有对不同构造函数的直接支持 - 这是Haskell为您跟踪的运行时信息。

因此,Leaf是一个 Node ,其构造函数为您初始化了颜色和高度字段,但使用的构造函数也作为创建对象的一部分进行跟踪。

与此最接近的等价物,以及它为您提供的模式匹配,将是像 Boost.Variant 这样的变体类型,为您提供类似的东西:

// underlying record storage
template <typename Key, typename Value>
struct NodeBase {
    Color color_;
    size_t height_;
    Key key_;
    Value val_;
};
template <typename Key, typename Value>
struct Leaf: public NodeBase<Key,Value> {
    Leaf(Key k, Value v) : NodeBase{Color::Black, 0, k, v} {}
    // default other ctors here
};
// similarly for your BlackBranch and RedBranch constructors
template <typename Key, typename Value>
using Node = boost::variant<Leaf<Key,Value>,
                            RedBranch<Key,Value>,
                            BlackBranch<Key,Value>>;

再次注意,您的 Branch 类型具有 leftColor、rightColor、childHeight 的记录,并且只有键和值创建类型参数。

最后,在 Haskell 中使用模式匹配在不同的 Node 构造函数上编写函数的地方,您将使用

template <typename Key, typename Value>
struct myNodeFunction {
    void operator() (Leaf &l) {
        // use l.key_, l.value_, confirm l.height_==0, etc.
    }
    void operator() (RedBranch &rb) {
        // use rb.key_, rb.value_, confirm rb.color_==Color::Red, etc.
    }
    void operator() (BlackBranch &bb) {
        // you get the idea
    }
};

并像这样应用它:

boost::apply_visitor(myNodeFunction<K,V>(), myNode);

或者,如果您经常使用此模式,则可以将其包装为

template <typename Key, typename Value,
          template Visitor<typename,typename> >
void apply(Node<Key,Value> &node)
{
    boost::apply_visitor(Visitor<Key,Value>{}, node);
}