Building a tree cleanly

Pages: 12
May 28, 2011 at 8:42am
I'm looking for a way to write a tree so that it does not look ugly.
Let's say I have
1
2
struct Node{ /*abstract*/ };
struct Branch : Node{ std::vector< Node* > v; };
and several non abstract classes Apple, Banana, Candy : Node and Donkey, Elk : Branch.

Now I want a tree from these. The prettiest way I can think of is lisplike
my_tree = (Donkey Apple (Elk Banana) Candy)
and etc. I can't think of a good way to get something like that in C++ though.

I could write
1
2
template<typename T>
Class& operator << (T t){ v.push_back( new T(t) ); return *this; }
for Class= every child of Branch (I'm only going to have a few of them, so I guess it's ok) and get
my_tree = Donkey() << Apple() << ( Elk() << Banana() ) << Candy();
which looks somewhat acceptable but does way too much copies.. (unless half of them are inlined..)

Or I could have a
1
2
3
4
struct List{
   std::vector< Node* > v;
   List& operator << ( Node* ) { ... }
};
and then write constructors Class( const List& ) for Class= every child of Branch. Then I'd have
my_tree = new Donkey( List() << new Apple() << new Elk( List() << new Banana() ) << new Candy() );
which takes care of the copies and the silly template, but I'm not sure if I like all the List() and new..

I'd appreciate any suggestions on how to write this neatly.
May 28, 2011 at 1:15pm
You could use list initializers, a C++1x feature:
Then your constructor looks like:
 
List(std::initializer_list<Node*> list) : v(list) {}


And you use it like this:
 
my_tree = new Donkey({new Apple(), new Elk({new Banana(), new Candy()}), new Candy()});


And if you really hate all the "new" you could use variadic templates:
1
2
3
4
5
6
7
8
9
10
11
12
template<typename T, typename First, typename... Args>
std::list<T*> make_list()
{
return make_list<T, Args...>().push_front(new First());
}

// to terminate recursion
template<typename T>
std::list<T*> make_list()
{
return std::list<T*>();
}


Not sure this work, and I use list to efficiently use push_front. Normally rvalue optimisation will prevent from copying the list at each call
Last edited on May 28, 2011 at 1:20pm
May 28, 2011 at 3:02pm
For compile-time/static parsing, I would dig into boost/assign and see if you can adapt some of techniques for your tree:

1
2
3
4
5
6
7
#include <boost/assign.hpp>
#include <map>
int main()
{
   std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');
}
May 28, 2011 at 9:40pm
This uses your second interface and doesn't perform a single unnecessary copy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <iostream>
#include <vector>

#define DEF_PRINT_NODE(TypeName) \
    void print() const { std::cout << #TypeName; }

#define DEF_PRINT_BRANCH(TypeName) \
    void print() const \
    { \
        std::cout << #TypeName << "<"; \
        for (unsigned i=0; i<v.size(); i++) \
        { \
            if (i!=0) std::cout << " "; \
            v[i]->print(); \
        } \
        std::cout << ">"; \
    }

#define DEF_CLONE(TypeName) \
    TypeName * clone() const { return new TypeName(*this); }

#define DEF_SWAP_CLONE(TypeName) \
    TypeName * swap_clone() const \
    { \
        TypeName * ret = new TypeName; \
        ret->swap(const_cast<TypeName&>(*this)); \
        return ret; \
    }

struct Node
{
    virtual Node * clone() const=0;
    virtual void print() const=0;
    virtual ~Node() {}
};

struct Branch : Node
{
    std::vector<Node*> v;

    virtual Branch * swap_clone() const=0;
    void swap(Branch & b) { v.swap(b.v); }
};

Branch & operator << (const Branch & cb1, const Branch & cb2)
{
    Branch & b1=const_cast<Branch&>(cb1);

    b1.v.push_back(cb2.swap_clone()); return b1;
}

Branch & operator << (const Branch & cb, const Node & cn)
{
    Branch & b=const_cast<Branch&>(cb);

    b.v.push_back(cn.clone()); return b;
}

struct Candy : Node { DEF_CLONE(Candy) DEF_PRINT_NODE(Candy) };
struct Apple : Node { DEF_CLONE(Apple) DEF_PRINT_NODE(Apple) };
struct Banana : Node { DEF_CLONE(Banana) DEF_PRINT_NODE(Banana) };

struct Donkey : Branch { DEF_SWAP_CLONE(Donkey)
     DEF_CLONE(Donkey) DEF_PRINT_BRANCH(Donkey) };

struct Elk : Branch { DEF_SWAP_CLONE(Elk)
     DEF_CLONE(Elk) DEF_PRINT_BRANCH(Elk) };

struct Tree : Branch { DEF_SWAP_CLONE(Tree)
     DEF_CLONE(Tree) DEF_PRINT_BRANCH(Tree) };

int main()
{
    Tree my_tree;

    my_tree.swap(

        Tree()
            << ( Donkey()
                    <<   Apple() )
            <<   Candy()
            << ( Elk()
                    <<   Banana()
                    <<   Apple() )
            << ( Donkey()
                    << ( Elk()
                            << Candy()
                            << Banana() )
                    <<   Apple() )

    );

    my_tree.print();

    return 0;
}

Last edited on May 29, 2011 at 3:18pm
May 29, 2011 at 7:56am
aquaz, that's neat, but I'd rather stick to the current standard..

kfmfe, the problem is that I have many different types (if I do it like my first example) or that you can't overload operators for two pointers (if I accept having new all over the place).
Also, why would you say compile-time ? It all seems quite run-time to me..

master roshi, this is awesome, I'm not sure if I'm going to use this, but still, awesome.
May 29, 2011 at 8:57am
Since your collections are collections of pointers, why do you care about the copying? Just modify your original idea and make operator<< take pointers by value.

Class& operator << (node* n){ v.push_back( n ); return *this; }

Then just use new:

my_tree = Donkey() << new Apple() << ( new Elk() << new Banana() ) << new Candy();

Or did I miss something?
May 29, 2011 at 1:27pm
webJose, that would not compile (I think), as the left argument of << after Elk() is a pointer. If you removed the new before Elk, the right argument of the << before it would not be a pointer.
It would be awesome if it really worked that way though..
May 29, 2011 at 4:06pm
Hi. I think it does compile. Check out a simple example: https://ideone.com/SSWtl. Surely it doesn't have complex inheritance in place, but I believe it shows the operator works on pointers to Node. It works because operator << is evaluated left to right.
May 29, 2011 at 4:18pm
If it does compile, it doesn't work. You're just shifting pointers, not calling overloaded operators.
May 29, 2011 at 4:19pm
Not sure I follow. Doesn't the output confirm the overload is being called???

EDIT: I'll add some confirmation by outputting the vector.
Last edited on May 29, 2011 at 4:20pm
May 29, 2011 at 4:25pm
I just added the printing of the list contents. The nodes seem to be OK. No pointer shifting.
May 29, 2011 at 5:05pm
I went further to clear it up beyond a doubt. Now the sample code prints the pointer addresses when the objects are created and when the objects are received inside operator<<. They are the same. Furthermore, it works OK with derived objects.

This should then allow for easy construction of the tree without the multiple constructors.
May 30, 2011 at 8:19am
https://ideone.com/SSWtl
You misunderstood. That code is fine. It's the parentheses that wouldn't compile.
Compile it with this main:
1
2
3
4
int main(){
    Branch B = Branch() << new Node(1) << (new Branch() << new Node(2)) << new Node(3);
    return 0;
}
I get
error C2296: '<<' : illegal, left operand has type 'Branch *'


On topic, I discovered that I actually don't need that tree at all. It was a result of my sin no. 12 I guess..
May 31, 2011 at 9:36am
hamsterman wrote:
I discovered that I actually don't need that tree at all.

Regardless, I thought you guys might want to see this.
It's way cleaner and doesn't perform any copying at all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <vector>

#define DEF_PRINT_NODE(NodeType) void print() const { std::cout << #NodeType; }

#define DEF_PRINT_BRANCH(BranchType) \
    void print() const { \
        std::cout << #BranchType << "<"; \
        for (unsigned i = 0; i < v.size(); i++) { \
            if (i!=0) std::cout << " "; \
            v[i]->print(); } \
        std::cout << ">"; }

#define DEF_INSERT_NOD_TO_NOD(NodeType) Branch & \
    operator << (Branch & b, NodeType & n) { b.v.push_back(&n); return b; }

#define DEF_INSERT_NOD_TO_GEN(NodeType) template <class T> Branch & \
    operator << (BranchGenerator<T> & g, NodeType & n) { return g() << n; }

#define DEF_INSERT_GEN_TO_NOD(GeneratorType) template <class T> Branch & \
    operator << (Branch & b, GeneratorType<T> & g) { return b << g(); }

#define DEF_INSERT_GEN_TO_GEN(GeneratorType) template <class T1, class T2> Branch & \
    operator << (BranchGenerator<T1> & g1, GeneratorType<T2> & g2) { return g1() << g2(); }

struct Node { virtual void print() const = 0; virtual ~Node() {} };

struct Branch : Node
{
    std::vector<Node*> v;

    ~Branch() { for (unsigned i = 0; i < v.size(); i++) delete v[i]; }
};

struct Candy  : Node { DEF_PRINT_NODE(Candy)  };
struct Apple  : Node { DEF_PRINT_NODE(Apple)  };
struct Banana : Node { DEF_PRINT_NODE(Banana) };

struct Donkey : Branch { DEF_PRINT_BRANCH(Donkey) };
struct Elk    : Branch { DEF_PRINT_BRANCH(Elk)    };
struct Tree   : Branch { DEF_PRINT_BRANCH(Tree)   };

template <class T> struct NodeGenerator   { Node   & operator()() { return *(new T); } };
template <class T> struct BranchGenerator { Branch & operator()() { return *(new T); } };

DEF_INSERT_NOD_TO_NOD(Node)          DEF_INSERT_NOD_TO_NOD(Branch)
DEF_INSERT_NOD_TO_GEN(Node)          DEF_INSERT_NOD_TO_GEN(Branch)
DEF_INSERT_GEN_TO_NOD(NodeGenerator) DEF_INSERT_GEN_TO_NOD(BranchGenerator)
DEF_INSERT_GEN_TO_GEN(NodeGenerator) DEF_INSERT_GEN_TO_GEN(BranchGenerator)

NodeGenerator<Candy>     _candy; NodeGenerator<Apple> _apple; NodeGenerator<Banana> _banana;
BranchGenerator<Donkey> _donkey; BranchGenerator<Elk>   _elk; BranchGenerator<Tree>   _tree;

Tree * make_tree(Branch & b) { return &dynamic_cast<Tree&>(b); }

int main()
{
    Tree * my_tree = make_tree (

        _tree
            <<   _banana
            << ( _donkey
                    <<   _apple
                    <<   _candy )
            <<   _candy
            << ( _donkey
                    << ( _elk
                            <<   _apple
                            <<   _banana )
                    <<   _candy )
            <<   _apple

    );

    my_tree->print();

    delete my_tree;

    return 0;
}

Needless to say, I had a spiritual orgasm after I finished this.
I was sitting on my chair just looking at it and thinking
"Oh, God, this is so beautiful... Why is this so beautiful?"
for like half an hour... (or maybe a little bit more...)
Last edited on May 31, 2011 at 12:53pm
May 31, 2011 at 10:39am
Needless to say, I had a spiritual orgasm after I finished this.
I can see why you would..

Though this looses the ability to call constructors..
May 31, 2011 at 12:44pm
hamsterman wrote:
Though this looses the ability to call constructors..

Oh... True... Well, I guess this can be solved by adding one more level of
indirection -> Generators that generate generators through operator().

Then, one can simply write:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Tree * my_tree = make_tree (

    _tree(8)
        <<   _banana(4)
        << ( _donkey(2)
                <<   _apple(3)
                <<   _candy(7) )
        <<   _candy(9)
        << ( _donkey(4)
                << ( _elk(1)
                        <<   _apple(6)
                        <<   _banana(0) )
                <<   _candy(4) )
        <<   _apple(5)

);

Last edited on May 31, 2011 at 12:57pm
May 31, 2011 at 1:14pm
Or functions that return generators. Though I guess you would then have to do the constness hack you did in the previous code.
May 31, 2011 at 2:01pm
hamsterman wrote:
Though I guess you would then have to do the constness hack you did in the previous code.

Mmmm... Not really.

The only reason I omitted the const qualifiers for the generator classes was laziness.
A generator doesn't have internal state and even if I add one (e.g. the Node/Branch
constructor arguments) the << operators won't modify it. It should be ok :D
Last edited on May 31, 2011 at 2:03pm
May 31, 2011 at 4:15pm
Here's mine.
It doesn't seem to abuse copying of any sort and with this you can ( but don't have to ) call constructors.
Awesome, eh?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <vector>

#define PRINT_NODE(Name) virtual void print() {\
   std::cout << #Name;\
 }
#define PRINT_BRANCH(Name) virtual void print() {\
   std::cout << #Name << "(";\
   for(unsigned i = 0; i < v.size(); i++) {\
      if(i) std::cout << ", ";\
      v[i]->print();\
   }\
   std::cout << ")";\
 }

struct Node{
   virtual void print() = 0;
   virtual ~Node() {};
};
struct Branch : Node{
   std::vector< Node* > v;
   virtual ~Branch(){
      for(unsigned i = 0; i < v.size(); i++)
         delete v[i];
   }
};

struct Apple : Node{
   int n;

   Apple( int N ) : n(N) {}

   virtual void print() {
      std::cout << n << " Apples";
   }
};
struct Banana : Node{ PRINT_NODE(Banana) };
struct Candy : Node{ PRINT_NODE(Candy) };

struct Donkey : Branch{ PRINT_BRANCH(Donkey) };
struct Elk : Branch{ PRINT_BRANCH(Elk) };
struct Tree : Branch{ PRINT_BRANCH(Tree) };

struct Pointer{
   Node* ptr;

   Pointer& operator /( Node* p ){
      ptr = p;
      return *this;
   }
};

#define APPLE  Pointer()/new Apple
#define BANANA Pointer()/new Banana
#define CANDY  Pointer()/new Candy

struct Container{
   Branch* ptr;

   operator Branch*(){
      return ptr;
   }
   Container& operator /( Branch* p ){
      ptr = p;
      return *this;
   }
   Container& operator , ( Pointer p ) { 
      if(ptr) ptr->v.push_back(p.ptr);
      return *this;
   }
   Container& operator , ( Container c ) { 
      if(ptr) ptr->v.push_back(c.ptr);
      return *this;
   }
};

#define DONKEY Container()/new Donkey
#define ELK    Container()/new Elk

int main(){
   Branch* tree = ( DONKEY , APPLE(5) , ( ELK , BANANA ) , CANDY );
   tree->print();
   std::cin.get();
   return 0;
}


How come this is so much fun..?
Last edited on May 31, 2011 at 4:16pm
Jun 1, 2011 at 10:59am
Ingenious!
Pages: 12