C ++中存在量化的等价物?

时间:2016-08-30 16:59:01

标签: c++ haskell c++14 existential-type

为了帮助自己教C ++,我正在开发一个红黑树实现。来自(哪里 我认为Haskell很有意思,看看我是否可以执行properties of a red-black tree 静态地在C ++的类型系统中:

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

我想出了如何为树的节点制作类型以满足约束条件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;
  };

  // ...
};

我被阻止的地方是root指针,它将存储在RedBlackTree中 实例一个满足属性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

回到haskell-land,我会用存在来解决这个问题 定量

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一个有用且正确的类型?

2 个答案:

答案 0 :(得分:4)

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

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

使用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 >。如果没有未定义的行为,则无法将此类型存储在未传递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>接口的内容的共享指针。

答案 1 :(得分:4)

注意

Yakk的回答是更惯用的C ++ - 这个答案显示了如何编写(或者至少开始编写)更类似于Haskell版本的东西。

当您看到模拟Haskell需要多少C ++时,您可以选择使用原生习语。

TL;博士

大多数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);
}