相当于C++的存在量化
Equivalent of existential quantification in C++?
为了帮助自学C++,我正在研究一个红黑树的实现。 来自哈斯克尔,我,认为看看我是否可以强制执行一个属性会很有趣红黑树静态地在C++的类型系统中:
- 节点为红色或黑色。
- 根是黑色的[...]
- 所有叶子(无(都是黑色的。
- 如果节点为红色,则其两个子节点均为黑色。
- 从给定节点到其任何后代 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
成员是根管理结构的成员。
您现在对泛型树具有只读访问权限。 在编译时,它由存储它的代码保证为平衡的红黑树。 在运行时,它只是保证是一棵树。
您可以将更多的保证信息泄漏到我上面写的界面中,但这会使界面混乱,超出读者通常需要知道的范围。 (例如,具有不同的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 >
. 无法在此共享指针中存储 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);
}
- C++模板来检查友元函数的存在
- 既然存在危险,为什么项目要使用-I include开关
- 我们可以访问一个不存在的联盟的成员吗
- C++:对不存在的命名空间使用命名空间指令
- C++quit()函数中可能存在作用域问题
- C++擦除(如果存在)
- 普通环路未使用gcc 4.8.5自动矢量化
- 基于树莓pi的tensorflow lite量化ssd目标检测
- g++ 说函数不存在,即使包含正确的标头
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 有了gcc,是否可以链接库,但前提是它存在
- C++LinkedList问题.数据类型之间存在冲突?没有匹配的构造函数
- gcc和clang在表达式是否为常量求值的问题上存在分歧
- C++Builder中的OnClick事件签名存在问题
- 如何正确地将分支添加到已存在的树中
- 我知道函数调用中存在歧义.有没有办法调用foo()函数
- 如何检查QList中是否存在值
- Visual Studio 2017循环自动向量化问题
- 根据某个函数是否存在启用模板
- 相当于C++的存在量化