3

How to get rid of abstract classes in the given implementation of self-referencing templates?

I just tried to implement a skip-list data structure. So I wanted to create the template Node such that I may instantiate the class of the next link for different node classes to avoid class casts. Have found these questions:

Self-referencing Template in Template Argument

How to properly declare a self-referencing template type?

but none of them have a solution. Then I've made my own solution based on two lines of inheritance. One is the sequence of "abstract" templates (for Next argument propogation). Another is to instantiate concrete classes. But feel like it can be improved to handle the same without redundant abstract templates (NodeAbstract, NodeWithKeyAbstract etc). After several own tries I want to ask you help me:

template <class Value, class Next >
class NodeAbstract
{
public:
    Value m_value;

    Next * next;

    NodeAbstract () : next(0) {}
    Next * getNext() {return next;}
};

template <class Value, class Key, class Next >
class NodeWithKeyAbstract : public NodeAbstract <Value, Next >
{
public:
    Key m_key;
};

template <class Value, class Key>
class NodeWithKey : public NodeWithKeyAbstract <Value, Key, NodeWithKey<Value,Key> >
{
};

template <class Value, class Key, int maxlevel, class Next>
class NodeSkipListAbstract : public NodeWithKeyAbstract<Value, Key, Next >
{
public:
    Next * nextjump[maxlevel-1];
};

template <class Value, class Key, int maxlevel>
class NodeSkipList : public NodeSkipListAbstract<Value, Key, maxlevel, NodeSkipList<Value, Key, maxlevel> >
{
};
Community
  • 1
  • 1
outmind
  • 759
  • 1
  • 10
  • 30
  • Help you with what exactly? Please list the errors. – Pradhan Mar 31 '15 at 18:48
  • I wanted to put it as an answer to the mentioned questions, but it looks like the authors of them wanted to achieve bit different things. – outmind Mar 31 '15 at 18:48
  • @Pradhan, thanks - just updated – outmind Mar 31 '15 at 18:50
  • 1
    @Pradhan: It's not a compiler error so much as a template can't contain it's own bits. `template> class Node;` – Mooing Duck Mar 31 '15 at 18:54
  • I think the normal workaround here is to make `template class Node {Next * next; virtual ~Node(){}` and then `dynamic_cast` or `static_cast` to derived types. – Mooing Duck Mar 31 '15 at 19:00
  • @MooingDuck, thanks. Normally I use the scheme you suggested. But currently I'm trying to avoid casting. I want to bind this Next argument to a class only when I really need it. – outmind Mar 31 '15 at 19:04
  • You should drop some Abstract and not design general nodes for a single linked list or a single linked list with keys (your design is too complicated) –  Mar 31 '15 at 19:06
  • @DieterLücking, I agree with you. In real-life projects I use "normal" KISS solutions. But here I'm trying to find a flexible template implementation. – outmind Mar 31 '15 at 19:12
  • 1
    Not sure if I understand what you try to achieve. But maybe going along the design of ATL could be what you need. Declare your interface ``IAbstractNode``, then create a class ``AbstractNodeImpl``. Then derive your actual class from ``AbstractNodeImpl`` and your actual class also inherits from ``IAbstractNode``. – BitTickler Apr 07 '15 at 23:25
  • 1
    It's really unclear what you're asking (cannot vote for closing this question as it has an open bounty). Perhaps you can give a simple use case? As it stands, `NodeAbstract` is only used as base for `NodeWithKeyAbstract` (and hence unnecessary); `NodeWithKey` is not used at all; `Next` is only ever of type `NodeSkipList`, so you don't really exploit self-referencing; finally *Abstract* is used for non-abstract entities. – Walter Apr 08 '15 at 13:56
  • Also, all that base-class design seems a bit off, given that in order to use a collection which has keys, the interface differs from a list-like interface. So all that inheriting seems redundant. The skiplist could be transparent to a single linked list, though and there, trying to create common code /might/ make sense. – BitTickler Apr 08 '15 at 16:36
  • @Walter, NodeWithKey and NodeSkipList are concrete classes to use in OrderedList and SkipList implementation. The problem is that if I try to get rid of NodeWithKey (i.e. try to merge NodeWithKey and NodeWithKeyAbstract), then it would be impossible in NodeSkipList to use NodeSkipList as the type for the next pointer field. – outmind Apr 09 '15 at 05:37

1 Answers1

1

If I understand you correctly, your problem is basically that different maxlevel values in would produce different classes, and so you couldn't use one array to store them all (correct me if I'm wrong).

You cannot fully get rid of abstract classes - if you want to have nodes with different max level as different classes (different template specializations) you have to provide some common denominator for them.

Good news is that you can get rid of Curiously Recurring Template Pattern instead - since you use pointers you don't have to refer to exact implementation type (e.g. knowing exact template specialization) if you're abstraction gives you access to all information you need. Also your code can be simplified a bit.

Consider this code:

template <class Key, class Value>
class Node {
 public:
  virtual ~Node() = default;

  virtual std::size_t MaxLevel() const = 0;
  virtual Node* Skip(size_t level) const = 0;
  // add setter as well

  Key key;
  Value value;
};

template <class Key, class Value, std::size_t max_level>
class NodeImpl : public Node<Key, Value> {
 public:
  typedef Node<Key, Value> node_type;

  NodeImpl() : skips() {}

  size_t MaxLevel() const { return max_level; }
  node_type* Skip(std::size_t level) const {
    return level < max_level ? skips[level] : nullptr;
  }
  // add setter as well

 private:
  node_type* skips[max_level];
};

template <class Key, class Value>
class SkipList {
  public:
   typedef Node<Key, Value> node_type;

   node_type* head;
};

Here Node provides you with an abstraction for a "skipping" behavior. NodeImpl would be used to generate Nodes with different max level, but in the end used implementation would be transparent to you - you would only use Node's interface. Also on syntax level you would only use Node* type, so variety of implementations wouldn't be a problem. Virtual destructor would ensure that delete frees all memory, and key and value would always be accessible as public fields.

This code can of course be improved. Raw array can be replaced by std::array. Whole idea of max_level as a template can be get rid of if you decide to use std::vector with size set in constructor instead of array (then you'll only have Node and SkipList). As a bonus creating new nodes would be easier, since now you'd have to write some factory with specializations of all NodeImpl's from 1 to some value. Additionally pointers could be replaced by some smart pointer to avoid memory leaks.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64