typedef in a templatised struct

Please can someone explain why this code won't compile?

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
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <stdio.h>

// typedef generalised callback types
template<class T>
struct cb0
{
    typedef void (T::*type)(); // member function of the form 'T::fn()'
};

template<class T>
void do_something(boost::shared_ptr<T> that, cb0<T>::type cb) // Line 14
{
    typedef boost::function<void()> func;
    func fn = boost::bind(that, cb);
    fn();
}

struct foo
{
    void bar()
    {
        printf("hello");
    }
};

int main()
{
    boost::shared_ptr<foo> p(new foo);
    do_something(p, &foo::bar); // Line 32
}


g++ reports:
Line 14: error: 'cb0::type' is not a type
Line 32: error: no matching function for call to 'do_something(boost::shared_ptr<foo>&, void (foo::*)())
Hmm... typename cb0<T>::type? I'm pretty sure you have to add typename somewhere, just not sure where.
Aaaaargh! You're 100% correct - I knew it as soon as I read your response! Thanks helios! :)
Anyway, what are you trying to do? Is this just an exercise?

(struct cb0 is very limiting -- needlessly so).
I'm trying to make a generalised thread-safe queue which is used to pass shared_ptrs around between objects.

Here's the code below (still not exactly working)

I've created code for 2 callback types, namely:
void T::fn()
void T::fn(shared_ptr<A0>)

This could be expanded to include more arguments (A1...An)
void T::fn(shared_ptr<A0>, shared_ptr<A1>)
void T::fn(shared_ptr<A0>, shared_ptr<A1>, shared_ptr<A2>)

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// cross-thread event queue. multiple threads can post jobs, one or many threads can execute
// jobs.
class EventQueue
{
public:
    typedef boost::function<void()> Job;

private:

    boost::mutex mtx_;
    boost::condition cnd_;
    typedef std::list<Job> Jobs;
    Jobs jobs_;
    int stop_;

    // puts a job into the queue
    void post(Job job)
    {
        boost::mutex::scoped_lock lock(mtx_);
        jobs_.push_back(job);
        cnd_.notify_one();
    }

public:

    // pulls one job from the queue, returns false when stopped
    bool pull(Job* job)
    {
        boost::mutex::scoped_lock lock(mtx_);
        for(;;)
        { // handle spurious wake-ups
            while(!stop_ && jobs_.empty())
                cnd_.wait(lock);
            if(!jobs_.empty() && 2 != stop_)
            {
                job->swap(jobs_.front()); // move efficiently, avoiding *job = jobs.front()
                jobs_.pop_front();
                return true;
            }
            else if(stop_)
            {
                return false;
            }
        }
    }

    // make pull() return false
    void stop(bool cancel_jobs)
    {
        boost::mutex::scoped_lock lock(mtx_);
        stop_ = 1 + cancel_jobs; // 1 - complete jobs, 2 - cancel jobs
        cnd_.notify_all();
    }

    EventQueue() : stop_() {}
    ~EventQueue() { this->stop(true); }

    // event post friends
    friend void post_to_event(boost::shared_ptr<event> &ev);
    template<typename A0>
    friend void post_to_event(boost::shared_ptr<event> &ev, boost::shared_ptr<A0> &data);
};
//-----------------------------------------------------------------------

// typedef generalised callback types
template<class T>
struct cb0
{
    typedef void (T::*type)(); // member function of the form 'T::fn()'
};

template<class T, typename A0>
struct cb1
{
    typedef void (T::*type)(boost::shared_ptr<A0>); // member function of the form 'T::fn(shared_ptr<A0>)'
};
//-----------------------------------------------------------------------

// the event class which keeps track of our job and the queue we want to post the job to
class event
{
    EventQueue &queue_;                     // the event queue which will process the job

    EventQueue::Job job_;

    event(EventQueue &queue, EventQueue::Job &job) : queue_(queue), job_(job) { }

    // event creation friends
    template<typename T>
    friend boost::shared_ptr<event> create_event(boost::shared_ptr<T> that, typename cb0<T>::type cb, EventQueue &queue);
    template<typename T, typename A0>
    friend boost::shared_ptr<event> create_event(boost::shared_ptr<T> that, typename cb1<T, A0>::type cb, EventQueue &queue);

    // event post friends
    friend void post_to_event(boost::shared_ptr<event> &ev);
    template<typename A0>
    friend void post_to_event(boost::shared_ptr<event> &ev, boost::shared_ptr<A0> &data);
};

// helper functions which create new events
template<typename T>
boost::shared_ptr<event> create_event(boost::shared_ptr<T> that, typename cb0<T>::type cb, EventQueue &queue)
{
    // encapsulate an object and it's member function using bind
    return boost::shared_ptr<event>(new event(queue, boost::bind(cb, that)));
}

template<typename T, typename A0>
boost::shared_ptr<event> create_event(boost::shared_ptr<T> that, typename cb1<T, A0>::type cb, EventQueue &queue)
{
    // encapsulate an object and it's member function using bind, placeholder for argument to come
    return boost::shared_ptr<event>(new event(queue, boost::bind(cb, that, _1)));
}
//-----------------------------------------------------------------------

// helper functions which post events onto the event's queue
void post_to_event(boost::shared_ptr<event> &ev)
{
    ev->queue_.post(ev->job_);
}

template<typename A0>
void post_to_event(boost::shared_ptr<event> &ev, boost::shared_ptr<A0> &data)
{
    // bind the data to the job
    ev->queue_.post(boost::bind(ev->job_, data));
}
//----------------------------------------------------------------------- 
Why do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
boost::shared_ptr<event> create_event(boost::shared_ptr<T> that, typename cb0<T>::type cb, EventQueue &queue)
{
    // encapsulate an object and it's member function using bind
    return boost::shared_ptr<event>(new event(queue, boost::bind(cb, that)));
}

template<typename T, typename A0>
boost::shared_ptr<event> create_event(boost::shared_ptr<T> that, typename cb1<T, A0>::type cb, EventQueue &queue)
{
    // encapsulate an object and it's member function using bind, placeholder for argument to come
    return boost::shared_ptr<event>(new event(queue, boost::bind(cb, that, _1)));
}


instead of just one create_event:

1
2
3
4
5
template< typename T, typename Fn >
boost::shared_ptr<event> create_event( boost::shared_ptr<T> that, Fn f, EventQueue& queue )
{
    return boost::shared_ptr<event>( new event( queue, f ) );
}


?

With your way, your user loses a lot of the flexibility of boost::function and boost::bind by virtue of
the cb0<T>::type typedef?

Of course, this means that your user has to write boost::bind() themself, but the added flexibility is
more than worth it.

1
2
3
4
5
6
7
// example usages:
boost::shared_ptr<event> ev( create_event( that, boost::bind( &MyObject::MyFunc, this, _1 ) ) );
boost::shared_ptr<event> ev( create_event( that, boost::bind( &MyObject::MyFunc, this, _1, 42 ) ) );
boost::shared_ptr<event> ev( create_event( that, boost::bind( &SomeFreeFunction, _1 ) ) );
boost::shared_ptr<event> ev( create_event( that, boost::bind( &SomeFreeFunction, _1, 42, "Hello World" ) ) );
boost::shared_ptr<event> ev( create_event( that, boost::bind( myFunctionObject, _1 ) ) );
// etc etc. 




I'm not sure that this will work:

instead of just one create_event:

1
2
3
4
5
template< typename T, typename Fn >
boost::shared_ptr<event> create_event( boost::shared_ptr<T> that, Fn f, EventQueue& queue )
{
    return boost::shared_ptr<event>( new event( queue, f ) );
}


The reason being, class event has a data member Job:

typedef boost::function<void()> Job;

Please correct me if I'm wrong, but if I use boost::bind with argument place holders (_1, _2 etc), won't the function signature used by boost::function be of the form void(A0), etc, rather than void()?

eg, for:

1
2
3
4
struct foo
{
    void bar(double d)
};


if I do the following bind:

1
2
foo f;
boost::bind(&foo::bar, f, _1);


To create a boost::function from that, it would have to be of the following form:

boost::function<void(double)> func = boost::bind(&foo::bar, f, _1);

I would not be able to do this:

Job job = boost::bind(&foo::bar, f, _1);

I guess I'm not following what all your code is doing.

When I read this from one of your previous replies:

I've created code for 2 callback types, namely:
void T::fn()
void T::fn(shared_ptr<A0>)

This could be expanded to include more arguments (A1...An)
void T::fn(shared_ptr<A0>, shared_ptr<A1>)
void T::fn(shared_ptr<A0>, shared_ptr<A1>, shared_ptr<A2>)


it seemed like it might be possible to condense all those functions down to 1.

When I create an event using the template< typename T, typename A0 > create_event function,
can I post to the event using the template< typename T > post_to_event function? Or do I have
to use the template< typename T, typename A0 > version?
No, I don't think it would make sense to be able to call post_to_event on a functor which expects an argument. My create_event and post_to_event helper functions were because I was battling to create elegant template classes to achieve what I'm looking for.

Perhaps if I elaborate I can make things clearer...
In my mind I've got this scenario: At startup, create an "event" which is associated with a queue, and a given class instance and member function.

Take the scenario where we're creating an event for a member function which takes a single argument of type A0. At startup I think it would make sense to create a functor of the form: boost::function<void(shared_ptr<A0>)> - the internals of this function object store the class instance and member function pointer. This function object is then stored inside our "event" object, which also stores a pointer to the queue we want to post data to.

We'll then use this "event" over and over during run time, as we receive data on one thread and "post" that data to another thread to be processed. Since this is a real time system we'll want to keep the amount of work needed to be done at run-time down to a bare minimum - if we can do the common setup tasks at startup (ie: creating our boost::function<void(shared_ptr<A0>)> fun; function object) that's ideal. So at run-time, all we now need to do is call boost::bind(fun, data) where fun is our function object that takes a single argument, and data is the argument, and pass the new function object (which is now of the form boost::function<void()>) to our queue.

Ideally I'd like to hide these implementation details from the user. Therefore all the user has to do is:
at start up:
1
2
3
EventQueue queue;
MyClass obj;
event<MyClass, MyData> ev = new event(obj, &MyClass::Func, queue);


at run time, over and over:
we receive some data, perhaps from a socket.
1
2
shared_ptr<A0> data( new A0(msg) ); // convert msg (received from socket) into A0 struct form
ev->post(data);


How to achieve this... I don't know! :(
Have you looked at all at the boost signal library? Does it do a large part of what you want? Or boost threads?

I'm not sure I quite understand. Somebody creates an event<>, which is just a container for the function to call later,
and the queue on which the event should be placed. I don't understand the queue concept because in your example
above, someone holds a pointer to the event in order to call post() on it, so I don't see the queue behavior. I also
don't understand what a Job is as opposed to an event<>. An event<> seems to me to be more of a "slot" to use
the boost::signals term.

don't understand what a Job is as opposed to an event<>


Yeah, sorry - events and jobs are the same thing... I should stick to one term. I've posted new code below which uses a consistent naming convention.

Have you looked at all at the boost signal library?


I'm a total noob with boost, so forgive me if I'm completely wrong here, but I don't think signals and slots would work. My concept is: thread1 has some data which it wants thread2 to process asynchronously. I think the signal/slot concept is not about passing data from 1 thread to another - it's about having a managed callback facility, is it not? So thread1 calling signal on a slot would just execute the slot in thread1's context.

I don't understand the queue concept


The queue is required because of the need to pass the data from 1 thread to another (asynchronously). I know I could just use bind to bind a member function and a class instance together and bind the argument too at the point at which I want to pass the data from thread1 to thread2, but I find this inelegant.

I know that thread1 is going to be receiving data packets over and over again, and wants to pass them asynchronously to thread2 every time it receives said data packet - it needs to queue these packets. I also know which member function I want to process these packets. My job class stores information about the queue which these packets get posted to, and the member function which will process these packets. All thread1 needs to do when it receives a packet then, is to call job::post(data_packet) and under the hood, the data_packet is bound to the member_function and the resulting function object passed to the queue.

You'll see in the code below I am able to do this, to a degree (I've provided code which allows for void T::process(), void T::process(shared_ptr<A0> data) and void T::process(shared_ptr<A0> data0, shared_ptr<A1> data1)

What I'd ideally like to do though, is to provide for generalised jobs - ie: create a templatised class which, when instantiated, creates a function object which exposes void operator(some arguments here) and when that is called, it binds the arguments to a member function specified at construction time and posts it to a queue (also specified at construction time)

Not sure if I've managed to make that any clearer!

So - my new code:

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#include <boost/enable_shared_from_this.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <list>

// cross-thread job queue. multiple threads can post jobs, one or many threads can execute jobs.
class job_queue
{
public:
    typedef boost::function<void()> Job;

private:

    boost::mutex mtx_;
    boost::condition cnd_;
    typedef std::list<Job> Jobs;
    Jobs jobs_;
    int stop_;

public:

    // puts a job into the queue
    void post(Job job)
    {
        boost::mutex::scoped_lock lock(mtx_);
        jobs_.push_back(job);
        cnd_.notify_one();
    }

    // pulls one job from the queue, returns false when stopped
    bool pull(Job* job)
    {
        boost::mutex::scoped_lock lock(mtx_);
        for(;;)
        { // handle spurious wake-ups
            while(!stop_ && jobs_.empty())
                cnd_.wait(lock);
            if(!jobs_.empty() && 2 != stop_)
            {
                job->swap(jobs_.front()); // move efficiently, avoiding *job = jobs.front()
                jobs_.pop_front();
                return true;
            }
            else if(stop_)
            {
                return false;
            }
        }
    }

    // make pull() return false
    void stop(bool cancel_jobs)
    {
        boost::mutex::scoped_lock lock(mtx_);
        stop_ = 1 + cancel_jobs; // 1 - complete jobs, 2 - cancel jobs
        cnd_.notify_all();
    }

    job_queue() : stop_() {}
    ~job_queue() { this->stop(true); }
};
//-----------------------------------------------------------------------

// create a thread which loops indefinitely, processing jobs off the queue

#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

void loop(job_queue* queue)
{
    std::cout << __PRETTY_FUNCTION__ << " thread id is: " << boost::this_thread::get_id() << std::endl;
    job_queue::Job job;
    // wait and execute jobs till stopped
    while(queue->pull(&job))
        job(); // execute the job
}
//-----------------------------------------------------------------------

boost::thread create_job_thread(job_queue* queue)
{
    return boost::thread(boost::bind(loop, queue));
}
//-----------------------------------------------------------------------

// various job types (for different argument types)

// member function of the form 'T::fn()'
template<class T> struct cb0 { typedef void (T::*type)(); };

template <typename T>
class job_0
{
    job_queue* queue;

    typedef boost::function<void()> post_func;
    post_func p_func;

public:
    job_0(boost::shared_ptr<T> that, typename cb0<T>::type cb, job_queue* q) :
        queue(q), p_func(boost::bind(cb, that)) {}

    void post()
    {
        queue->post(p_func);
    }
};

// member function of the form 'T::fn(shared_ptr<A1>)'
template<class T, typename A1> struct cb1 { typedef void (T::*type)(boost::shared_ptr<A1>); };

template <typename T, typename A0>
class job_1
{
    job_queue* queue;

    typedef boost::function<void(boost::shared_ptr<A0>)> post_func;
    post_func p_func;

public:
    job_1(boost::shared_ptr<T> that, typename cb1<T, A0>::type cb, job_queue* q) :
        queue(q), p_func(boost::bind(cb, that, _1)) {}

    void post(boost::shared_ptr<A0> data)
    {
        queue->post(boost::bind(p_func, data));
    }
};

// member function of the form 'T::fn(shared_ptr<A1>, shared_ptr<A2>)'
template<class T, typename A1, typename A2> struct cb2 { typedef void (T::*type)(boost::shared_ptr<A1>, boost::shared_ptr<A2>); };

template <typename T, typename A0, typename A1>
class job_2
{
    job_queue* queue;

    typedef boost::function<void(boost::shared_ptr<A0>, boost::shared_ptr<A1>)> post_func;
    post_func p_func;

public:
    job_2(boost::shared_ptr<T> that, typename cb2<T, A0, A1>::type cb, job_queue* q) :
        queue(q), p_func(boost::bind(cb, that, _1)) {}

    void post(boost::shared_ptr<A0> data1, boost::shared_ptr<A0> data2)
    {
        queue->post(boost::bind(p_func, data1, data2));
    }
};
//-----------------------------------------------------------------------

// example usage

struct data
{
    int i;
    double d;
    float f;
    std::string s;

    data() : i(), d(), f(), s()
    {
        std::cout << "data ctor " << this << " on thread " << boost::this_thread::get_id() << std::endl;
    }

    void ptr()
    {
        std::cout << "data " << this << std::endl;
        std::cout << "i=" << i << std::endl;
        std::cout << "d=" << d << std::endl;
        std::cout << "f=" << f << std::endl;
        std::cout << "s=" << s << std::endl;
    }

    ~data()
    {
        std::cout << "~data dtor " << this << " on thread " << boost::this_thread::get_id() << std::endl;
    }
};

struct consumer
    : boost::enable_shared_from_this<consumer>
{
    typedef boost::shared_ptr<consumer> consumer_ptr;
    typedef job_1<consumer, data> consumer_job;
    
    boost::shared_ptr<consumer_job> job_;

    consumer(job_queue &queue) :
        job_(new consumer_job(consumer_ptr(this), &consumer::eat, &queue))
    { }

    void eat(boost::shared_ptr<data> d)
    {
        std::cout << __PRETTY_FUNCTION__ << " " << this << " on thread " << boost::this_thread::get_id() << std::endl;
        d->ptr();

        if (d->i++ < 5)
        {
            d->d *= 2;
            d->f /= 2;
            job_->post(d);
        }
    }
};

int main()
{
    std::cout << __PRETTY_FUNCTION__ << " thread id is: " << boost::this_thread::get_id() << std::endl;

    job_queue queue;

    // create a thread which processes data received from a queue
    boost::thread another_thread = create_job_thread(&queue);

    boost::shared_ptr<consumer> c(new consumer(queue));

    // create data in this thread
    boost::shared_ptr<data> d(new data);
    d->i = 0;
    d->d = 20.5;
    d->f = 3.4;
    d->s = "hello, world";

    // post job to another thread for asynchronous processing
    c->job_->post(d);

    // stop the queue and let it complete all jobs
    queue.stop(false);
    another_thread.join();

    return 0;
}
//-----------------------------------------------------------------------
Last edited on
Sorry, been thinking about this occasionally and I've been very busy.

I think I get it now.

I'm not sure I have a good answer without a bit of a redesign.

This is very similar to message handling. One way to implement it is to have each
message, or in your case, event, have an execute() method which might take a
parameter or two. The event then knows how to handle itself. The parameters
to execute() might be references to other objects that it needs to get data from.

Essentially, I think in your design, you have an object that knows how to handle
the event by accessing the data members of the event. What I'm saying is the
event knows how to handle itself, by perhaps calling functions in the other object.
Just flipping it around.

Does that make sense? Is that something doable?
Do you think you could give a code example?
Stupid little example, but hopefully demonstrates the idea.

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
#include <algorithm>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <deque>
#include <iostream>
#include <set>

class MyThread {
    std::set<int> v;

  public:
    void AddInt( int i )
        { v.insert( i ); }

    bool HasInt( int i ) const
        { return v.find( i ) != v.end(); }
};


struct AddInt {
    explicit AddInt( int i ) : i( i ) {}

    void Execute( MyThread& thd ) const {
         thd.AddInt( i );
    }

    int i;
};


struct AddInts {
    template< typename Iter >
    AddInts( Iter first, Iter last ) : is( first, last ) {}

    void Execute( MyThread& thd ) const {
         std::for_each( is.begin(), is.end(),
             boost::bind( &MyThread::AddInt, boost::ref( thd ), _1 ) );
    }

    std::deque<int> is;
};


struct FindInt {
    explicit FindInt( int i ) : i( i ) {}

    void Execute( MyThread& thd ) const {
        std::cout << i << " is" << ( thd.HasInt( i ) ? " " : " not " )
            << "in there" << std::endl;
    }

    int i;
};


int main() {
    boost::array<int, 10> ar = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 };

    MyThread thd1;

    AddInt   msg1( 4 );
    AddInt   msg2( 8 );
    AddInt   msg3( 10 );
    AddInts  msg4( ar.begin(), ar.end() );
    FindInt  msg5( 7 );
    FindInt  msg6( 12 );

    msg1.Execute( thd1 );
    msg2.Execute( thd1 );
    msg3.Execute( thd1 );
    msg4.Execute( thd1 );
    msg5.Execute( thd1 );
    msg6.Execute( thd1 );
}


Note that each command knows how to execute itself. Commands don't have all the details; they must call functions
in another object to help them execute.
Topic archived. No new replies allowed.