To help teach myself C++, I'm working on a red-black tree implementation. Coming from Haskell, I, thought it'd be interesting to see if I could enforce the properties of a red-black tree statically in C++'s type system:
- A node is either red or black.
- The root is black [...]
- All leaves (NIL) are black.
- If a node is red, then both its children are black.
- Every path from a given node to any of its descendant NIL nodes contains the same number of black nodes. [...]
I figured out how to craft the types for the nodes of the tree to satisfy constraints 1, 3, 4, and 5 using templates:
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;
};
// ...
};
Where I'm stymied is giving the root
pointer that will be stored in the RedBlackTree
instance a type that satisfied property 2 but is still useful.
What I want is something like:
template <typename Key, typename Value>
class RedBlackTree {
//...
unique_ptr<Node<color::Black,?>> root = std::make_unique<Leaf>();
//...
}
(to borrow syntax from Java), so I can wildcard over the height of the tree. This of course, doesn't work.
I could get my code to compile if I did
template <typename Key, typename Value, size_t TreeHeight>
class RedBlackTree {
//...
unique_ptr<Node<color::Black,TreeHeight>> root = std::make_unique<Leaf>();
//...
}
But this isn't the type I want for the tree - I don't want the type of the tree itself
to reflect its height, otherwise the type of my tree might change when I insert a
key-value pair. I want to be able to update my root
to contain a pointer to a black
Node
of any height.
Back in haskell-land, I would have solved this problem using existential quantification:
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
Is there an equivalent concept in C++14 (or maybe C++17), or an alternative way I could write my struct
defintions to be able to give root
a useful and correct type?
TreeHeight
to be "erased" from the type of the parent node (to not be part of its type)? If so, you need some sort of run-time dispatch – Vittorio Romeo