为了帮助自己教C ++,我正在开发一个红黑树实现。来自(哪里 我认为Haskell很有意思,看看我是否可以执行properties of a red-black tree 静态地在C ++的类型系统中:
- 节点为红色或黑色。
- 根是黑色[...]
- 所有叶子(NIL)都是黑色。
- 如果节点为红色,则其子节点均为黑色。
- 从给定节点到其任何后代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;
};
// ...
};
我被阻止的地方是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
一个有用且正确的类型?
答案 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
成员是您的根管理结构的成员。
您现在拥有对通用树的只读访问权限。它在编译时由存储它的代码保证是一个平衡的红黑树。在运行时,它只能保证一棵树。
您可能会将更多保证信息泄漏到我上面写的界面中,但这会使界面混乱超出读者通常需要知道的范围。 (比如,有一个不同的Red
和Black
接口节点类型。)
现在,如果一个简短函数的主体太过信任,而你宁愿相信一堆模板代码,那么我们可以这样做:
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 ++时,您可以选择使用原生习语。
大多数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);
}