1

I have a situation where I fall foul of the aforementioned issue. I would like to construct a tree with associated nodes. Since all trees will behave the same but with different types, in the spirit of inheritance I would like to be able to construct trees with different types using the same tree construction routine defined by the base class. I would like to know what the best practice is for my situation.

struct Tree
{
    struct Node { std::list<std::shared_ptr<Node>> children_; };

    Tree()
    {
        root_ = CreateNode();

        // carry on adding nodes to their parents...
    }

    virtual std::shared_ptr<Node> CreateNode() { return std::shared_ptr<Node>(new Node()); }

    std::shared_ptr<Node> root_;
};

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived() : Tree() {}

    virtual std::shared_ptr<Node> CreateNode() { return std::shared_ptr<NodeDerived>(new NodeDerived()); }
};

The problem is that I cannot call derived functions until the base is constructed (obviously), and that uses the base implementation of the CreateNode method which always constructs trees with the base node implementation. This means I can construct trees without delaying tree population. The obvious solution is to template the tree to take different node types, enforcing the node type using traits? However, this also means definition of all methods would have to be in the header? The class is quite meaty so I would like to avoid this if possible, so I thought about passing a lambda which does this for me.

struct Tree
{
    struct Node { std::list<std::shared_ptr<Node>> children_; };

    Tree(std::function<std::shared_ptr<Node>()> customNodeConstructor)
    {
        root_ = customNodeConstructor();

        // carry on adding nodes to their parents... using the customNodeConstructor to create the nodes.
    }

    std::shared_ptr<Node> root_;
};

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived(std::function<std::shared_ptr<Node>()> customNodeConstructor) : Tree(customNodeConstructor) {}
};

Which allows me to derive and pass a customNodeConstructor relevant to the derived tree. Type safety is partially enforced since the returned shared_ptr object must derive from Tree::Node, although isn't enforced to the derived node type.

i.e a TreeDerived instantiation, which should perhaps use TreeDerived::NodeDerived is only enforced to use Tree::Node or derived type, but not necessarily TreeDerived::NodeDerived.

This can then be used like so...

Tree tree([]() { return std::shared_ptr<Tree::Node>(); });

TreeDerived treeDerived([]() { return std::shared_ptr<TreeDerived::NodeDerived>(); });

Is this good practice or should I be doing more/something else without templating the Tree object?

Many thanks.

lfgtm
  • 1,037
  • 6
  • 19
  • Have you considered templates? `template struct Tree;`? – YSC Aug 24 '18 at 11:36
  • @YSC thanks, yes templating is the obvious solution to this, but I would like to avoid that if I can since there is a fair bit of code that I would like to avoid putting in the header. – lfgtm Aug 24 '18 at 11:43
  • 2
    You don't _have to_ as long as you explicitly specialize your template with all the types it'll be used with. See https://stackoverflow.com/a/495056/5470596 – YSC Aug 24 '18 at 11:44
  • Why not simply have different trees of different things? – Jesper Juhl Aug 24 '18 at 11:46
  • @YSC ahh ok, didn't know this could be done. I always thought implementation of templates needed to be done in the header. nice! – lfgtm Aug 24 '18 at 11:53
  • This is why love C++: you can learn something new each day for your entire life. – YSC Aug 24 '18 at 11:54
  • @JesperJuhl I'm trying to avoid code duplication. My tree type (which is used in a BSP context) is always going to behave the same way, just using different types for its nodes. – lfgtm Aug 24 '18 at 11:55

2 Answers2

2

The idea is sound; however, it would be safer to create the "custom tree constructor" inside the derived class instead of taking it externally. That way, you cannot get an incorrect node type. In code form:

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived() : Tree([]() { return std::make_shared<NodeDerived>(); }) {}
};

Also note that in the general case, std::function incurs a non-trivial runtime overhead for each call. If you're always going to pass a stateless lambda in, consider taking a plain function pointer in the Tree constructor instead:

Tree(std::shared_ptr<Node> (*customNodeConstructor)())
{
    root_ = customNodeConstructor();

    // carry on adding nodes to their parents... using the customNodeConstructor to create the nodes.
}

As an alternative to this "pass in a customised creator" approach, you could also turn only the constructor of Tree into a function template. It could then look like this:

template <class T> struct TypeTag;

struct Tree
{
    struct Node { std::list<std::shared_ptr<Node>> children_; };

    template <class ConcreteNode>
    Tree(TypeTag<ConcreteNode>)
    {
        root_ = std::make_shared<ConcreteNode>();

        // carry on adding nodes to their parents...
    }

    std::shared_ptr<Node> root_;
};

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived() : Tree(TypeTag<NodeDerived>{}) {}
};

The constructor template would then have to be defined someplace where all classes derived from Tree can see its definition (so likely in a header files), but the rest of the Tree class remains normal.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Brilliant thankyou, yes this makes the caller a lot cleaner and can enforce some the type. :) – lfgtm Aug 24 '18 at 11:45
0

It is hard for me to understand why do you require design you do(struct inside derived struct deriving from the struct inside base class looks questionable to say the least).

That being said you could consider workarounds from mclow's FAQ.

tl;dr is that you move logic from constructor into init() and inside init() virtual function calls "work".

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • Thanks for your answer. "struct inside derived struct deriving from the struct inside base class" is purely to inform about type association with its other type and rather than polluting the namespace. There is no particular reason to this in terms of design, just so the use of Node types with the Tree type while allowing it to be derived. Of course this could be placed in the namespace? Is there a problem with nesting these declarations? – lfgtm Aug 28 '18 at 10:55
  • If I'm reading that link correctly. Moving from constructor into an init requires a 2 phase construction which violates RAII which I would like to enforce. The other option mentioned is similar to the one above that I used but rather than passing a Helper class, I pass a callback to perform the 'derived' logic. Overall I think templating is the answer here. – lfgtm Aug 28 '18 at 10:56
  • @lfgtm read the part of the link that starts with "The second variation" – NoSenseEtAl Aug 28 '18 at 11:27