heterogeneous container

I tried to do something like this a while ago but it was kind of messy...
( http://cplusplus.com/forum/general/25840/#msg138101 )

I've been playing around with it and made some improvements. The access to the elements of the container is now done using a Visitor object, which internally holds a type->callback mapping and can thus perform the appropriate operation when given a specific type.

A known issue is that it doesn't work if a contained object overloads operator & to do something other than return its address, but I'm not worried about this too much, as I know ways to fix it.

What I'm interested in is what more design improvements can be made here.

object.h
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 <map>

class Object
{
private:

    struct AbstractObject
    {
        virtual ~AbstractObject() {}
        virtual void * get_data() {return 0;}
        virtual void * get_type() {return 0;}
        virtual AbstractObject * clone() const {return 0;}
    };

    template <class T>
    struct ConcreteObject : public AbstractObject
    {
        T data;
        static int type;

        ConcreteObject(const T & v):data(v) {}
        virtual ~ConcreteObject () {}
        virtual void * get_data() {return &data;}
        virtual void * get_type() {return &type;}
        virtual ConcreteObject * clone() const {return new ConcreteObject<T>(data);}

        static void * get_type_static() {return &type;}
    };

    AbstractObject * object;

    Object() {}

public:

    template <class T>
    Object(const T & v) {object=new ConcreteObject<T>(v);}
    Object(const Object & o) {object=o.object->clone();}
    ~Object() {delete object;}

    friend class Visitor;
};

template <class T>
int Object::ConcreteObject<T>::type;

class Visitor
{
public:

    struct Callback
    {
        virtual ~Callback() {}
        virtual void operator()(void *) {}
        virtual Callback * clone() const {return 0;}
    };

    typedef std::map<void *, Callback *> Dispatcher;

    Visitor() {}
    Visitor(const Visitor & v);
    ~Visitor();

    template <class T, template <class> class Cb>
    Visitor & operator<<(const Cb<T> & cb);

    void operator()(Object & o);

private:

    Dispatcher dispatcher;
};

template <class T, template <class> class Cb>
Visitor & Visitor::operator<<(const Cb<T> & cb)
{
    void * type=Object::ConcreteObject<T>::get_type_static();
    Dispatcher::iterator it=dispatcher.find(type);

    if (it!=dispatcher.end()) {delete it->second;}

    dispatcher[type]=cb.clone();

    return *this;
}

object.cpp
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
#include "object.h"

Visitor::Visitor(const Visitor & v)
{
    Dispatcher::const_iterator it=v.dispatcher.begin();

    while (it!=v.dispatcher.end()) {dispatcher[it->first]=it->second->clone(); it++;}
}

Visitor::~Visitor()
{
    Dispatcher::iterator it=dispatcher.begin();

    while (it!=dispatcher.end()) {delete it->second; it++;}
}

void Visitor::operator()(Object & o)
{
    void * type=o.object->get_type();
    Dispatcher::iterator it=dispatcher.find(type);

    if (it==dispatcher.end()) return;

    it->second->operator()(o.object->get_data());
}

main.cpp
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
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <algorithm>
#include <iterator>
#include "object.h"

template <class T>
struct DoIt : public Visitor::Callback
{
    void operator()(void * data) {std::cout << *(T*)data << std::endl;}
    DoIt * clone() const {return new DoIt<T>;}
};

template <>
struct DoIt<int> : public Visitor::Callback
{
    void operator()(void * data) {std::cout << *(int*)data+100 << std::endl;}
    DoIt * clone() const {return new DoIt<int>;}
};

int main()
{
    std::vector<Object> obj_vec;
    std::list<Object> obj_lst;

    obj_vec.push_back(std::string("asdf"));
    obj_vec.push_back(1);
    obj_vec.push_back(1.1);
    obj_vec.push_back(2);
    obj_vec.push_back(2.2);

    std::copy(obj_vec.begin(),obj_vec.end(),back_inserter(obj_lst));

    Visitor do_it;
    do_it
        << DoIt<int>()
        << DoIt<double>()
        << DoIt<std::string>();

    std::for_each(obj_lst.begin(),obj_lst.end(),do_it);

    return 0;
}

Last edited on
Neat. I'm not sure I know where that could be used, but still, neat.

One question though. What's template <class T, template <class> class Cb>? Does it mean that the second class has to be a template class? Would template <class T, class Cb> not work? It seems logical to allow a not type specific Callback to me.
I wonder if Visitor::operator << could be done with plain polymorphism. Maybe you could make Callback a template class?
I also wonder if you could then define clone in Callback. It could probably work if child classes had no members.. Though that is no good, of course.
hamsterman wrote:
Neat. I'm not sure I know where that could be used, but still, neat.

Thanx! ^^ Neither do I know where it can be used, I wrote it just for fun :D

hamsterman wrote:
One question though. What's template <class T, template <class> class Cb>? Does it mean that the second class has to be a template class? Would template <class T, class Cb> not work? It seems logical to allow a not type specific Callback to me.

Yeah, I should probably have said that earlier. I tried something like this at first:

1
2
template <class T, class Cb>
Visitor & AddCb(const Cb & cb);

But then I had to do something like this in main...

1
2
3
4
do_it.
    AddCb<int>(DoIt<int>()).
    AddCb<double>(DoIt<double>()).
    AddCb<std::string>(DoIt<std::string>());

If I make Cb a template class, I don't need to specify the type twice. That's why I did it.

hamsterman wrote:
Maybe you could make Callback a template class?

This is actually a very interesting idea. I guess I could keep my Callback as it is and also add a template subclass CallbackT. Then, in main, I could derive my concrete callbacks either from Callback or from CallbackT. Ok, while typing this I stopped for a while and started coding and I got this and it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//...

template <class T>
struct CallbackT : public Callback {};

//...

template <class T, class Cb>
Visitor & AddCb(const Cb & cb);

template <class T, template <class> class Cb>
Visitor & AddCb(const Cb<T> & cb);

//... 

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
//...

template <class T>
struct DoIt : public Visitor::Callback
{
    void operator()(void * data) {std::cout << *(T*)data << std::endl;}
    DoIt * clone() const {return new DoIt<T>;}
};

struct DoItInt : public Visitor::CallbackT<int>
{
    void operator()(void * data) {std::cout << *(int*)data+100 << std::endl;}
    DoItInt * clone() const {return new DoItInt;}
};

struct DoItChar : public Visitor::CallbackT<char>
{
    void operator()(void * data) {std::cout << char(*(char*)data+1) << std::endl;}
    DoItChar * clone() const {return new DoItChar;}
};

//...

    obj_vec.push_back(std::string("asdf"));
    obj_vec.push_back(1);
    obj_vec.push_back(1.1);
    obj_vec.push_back(2);
    obj_vec.push_back(2.2);
    obj_vec.push_back('A');

//...

    do_it.
        AddCb<int>(DoItInt()).
        AddCb<char>(DoItChar()).
        AddCb(DoIt<double>()).
        AddCb(DoIt<std::string>());

//... 

I lose the << operator syntax though, but I don't really care.
The annoying thing was having to write the type twice.

Thanks for the feedback! :)
Last edited on
Topic archived. No new replies allowed.