Execute List of Arbitrary Methods from Arbitrary Objects

Hello, I wish to implement a list of pointers which point to methods from a diverse collection of objects and then execute them in order.

To give some background, I am doing embedded development and I wish to attach different object functions to different Interrupt Handlers which will cycle through their respective lists and execute the given functions. I wish to be able to dynamically add and remove these functions from the lists.

The core problem that I have found is that there is no "datatype" type, so I cannot include a datatype along with the pointers in a struct in order to dynamically cast the pointers. One hacky way around this is to basically enumerate the objects and do a switch statement, casting the object pointers to the relevant object type in the ISR but this ugly.

Anyone suggestions? I asked on stack exchange and didn't get good advice.

Do you use C or C++ ?
C++ of course since the question is about objects.
I just wanted to be sure. Sometimes people use the term object for different things.
> The core problem that I have found is that there is no "datatype" type,
> so I cannot include a datatype along with the pointers in a struct in order to dynamically cast the pointers.

Wrap the call in a polymorphic call wrapper: https://en.cppreference.com/w/cpp/utility/functional/function

And bind the arguments to the call to generate an appropriate forwarding call:
https://en.cppreference.com/w/cpp/utility/functional/bind

For example:

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
#include <iostream>
#include <functional>
#include <map>
#include <vector>

using interrupt_handler_t = std::function< void() > ;

// key == interrupt number, mapped data == vector of interrupt handlers
using handler_map_t = std::map< unsigned int, std::vector<interrupt_handler_t> >  ;

// return number of handlers called
std::size_t try_handle_interrupt( unsigned int interrupt_number, const handler_map_t& handler_map )
{
    const auto iter = handler_map.find(interrupt_number) ;
    if( iter != handler_map.end() ) // if an entry is found
    {
        // call the handlers stored in the vector
        for( auto& handler : iter->second ) handler() ;
        return iter->second.size() ; // number of handlers
    }
    else return 0 ;
}

int main()
{
    struct A
    {
        void handle_interrupt_1() { std::cout << "A::handler for interrupt #1 A object: " << this << '\n' ; }
        void handle_interrupt_2() { std::cout << "A::handler for interrupt #2 A object: " << this << '\n' ; }
    };

    struct B
    {
        void handle_interrupt_1() { std::cout << "B::handler for interrupt #1 B object: " << this << '\n' ; }
        void handle_interrupt_2( int extra_arg )
        { std::cout << "B::handler for interrupt #2 B object: " << this << " extra_arg: " << extra_arg << '\n' ; }
    };

    A a1, a2 ;
    B b ;

    const handler_map_t handler_map =
    {
        // handlers for interrupt #1
        { 1, { std::bind( &A::handle_interrupt_1, std::ref(a1) ),
               std::bind( &B::handle_interrupt_1, std::ref(b) ) } },

        // handlers for interrupt #2
        { 2, { std::bind( &A::handle_interrupt_2, std::ref(a1) ),
               std::bind( &A::handle_interrupt_2, std::ref(a2) ),
               std::bind( &B::handle_interrupt_2, std::ref(b), 23 /* pass 23 as extra_arg */ ) } },
    };

    for( unsigned int interrupt_number = 1 ; interrupt_number < 4 ; ++interrupt_number )
    {
        std::cout << "interrupt_number #" << interrupt_number << '\n' ;
        if( const auto n = try_handle_interrupt( interrupt_number, handler_map ) )
            std::cout << "handled: " << n << " handlers were called\n" ;
        else std::cout << "unhandled: no entry in the map for this interrupt in the map\n" ;
        std::cout << "\n---------------------------\n" ;
    }
}

http://coliru.stacked-crooked.com/a/c9f36de19f1d3423
You could derive all of the interrupt handler from a common base class with a pure virtual function named execute. Override the execute function in each of the derived classes to to the appropriate thing.

Create a std::List<BaseInterruptHandler*> (or std::List<std::reference_wrapper<BaseInterruptHandler>>). Simply iterate through the list and call execute on each object in the list.
depending on what exactly you require besides just running a list,
a vector of function pointers seems perfectly valid for this, or fp + parameter object of some sort. Yes, its a bit C-ish, but there are times when you need a simple solution. And there are times when the simple solution is too simple, and won't fill your needs.

The casting problem requires a bit of work, no matter what approach.
Last edited on
I havent had a chance to debug it but this is my solution:

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
enum ObjectType{QuadratureEncoderType, MotorType, EncodedMotorType, PIDControllerType};

struct  MemberToRun
{
	ObjectType ObjectPtrType;
	void *ObjectPtr = NULL;
	void *MemberPtr = NULL;	
};

MemberToRun TC2_MemberList[10];

void AttachMemberToTC2(void *ObjectPtr, void *MemberPtr, ObjectType ObjectPtrType)
{
	static int index = 0;
	TC2_MemberList[index].ObjectPtr = ObjectPtr;
	TC2_MemberList[index].MemberPtr = MemberPtr;
	TC2_MemberList[index].ObjectPtrType = ObjectPtrType;
	index +=1;
}

int RemoveMemberFromTC2(void *ObjectPtr, void *MemberPtr)
{
	int index = 0;
	bool done = false;
	
	while(done == false)
	{
		if (TC2_MemberList[index].ObjectPtr == NULL)
		{
			return -1;
		}
		else if ((TC2_MemberList[index].MemberPtr == MemberPtr) && (TC2_MemberList[index].ObjectPtr == ObjectPtr))
		{
			done = true;
		}
		else
		{
			index +=1;
		}
	}
	
	while(TC2_MemberList[index+1].ObjectPtr != NULL)
	{
		TC2_MemberList[index] = TC2_MemberList[index+1];
		index +=1;
	}
	
	TC2_MemberList[index].MemberPtr = NULL;
	TC2_MemberList[index].ObjectPtr = NULL;
	TC2_MemberList[index].ObjectPtrType;
}

void UpdateMemberList(MemberToRun MemberList[])
{	 
	int index = 0;
	bool done = false;
	while (done == false)
	{
		ObjectType ObjectPtrType = MemberList[index].ObjectPtrType;
		switch (ObjectPtrType)
		{
			case QuadratureEncoderType:
			*((QuadratureEncoder*)MemberList[index].ObjectPtr).*(MemberList[index].MemberPtr)();
			break;
			
			case MotorType:
			(*((Motor*)MemberList[index].ObjectPtr)).*(MemberList[index].MemberPtr)();
			break;
			
			case EncodedMotorType:
			(*((EncodedMotor*)MemberList[index].ObjectPtr)).*(MemberList[index].MemberPtr)();
			break;
			
			case PIDControllerType:
			(*((PIDController*)MemberList[index].ObjectPtr)).*(MemberList[index].MemberPtr)();
			break;
		}
		
		index +=1;
	}	
}


The switch statement casting is giving me errors, I need to figure out how to fix it.
Last edited on
> I havent had a chance to debug it but this is my solution

It (the attempted conversion from a pointer to member function to pointer to void) would generate compile-time errors. A pointer to (possibly cv qualified) void can be used to hold only object pointers

1
2
3
4
5
6
7
8
9
10
int main()
{
    struct A { void mem_fun() const {} } ;

    const auto pmfn = &A::mem_fun ; // pointer to member function

    const void* pv = pmfn ; // *** error *** invalid conversion
    const void* pv1 = (const void*)pmfn ; // *** error *** invalid cast
    const void* pv2 = reinterpret_cast<const void*>(pmfn) ; // *** error *** invalid cast
}

http://coliru.stacked-crooked.com/a/b7764dd83a5a1ad2
I saw this, I haven't had a chance to figure out a good solution. The crux of the problem is there isn't a way to store or pass a member function pointer generically. Any ideas?

Also your solution is great but, I want a way to be able to attach/detach the function members dynamically by cycling through a list, I'm having trouble figuring out how to write a template which would allow this to be done automatically as it seems like it will require a bunch of special cases.
Last edited on
> is there isn't a way to store or pass a member function pointer generically. Any ideas?

A polymorphic call wrapper is generic.


> I want a way to be able to attach/detach the function members dynamically by cycling through a list,
> a template which would allow this to be done automatically...

We could allot an id to uniquely identify each handler that is added.
And provide a facility to detach a handler based on its unique id.

Here is some skeleton code which outlines this 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
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
#include <iostream>
#include <functional>
#include <map>
#include <vector>

// first: a unique handler number to identify this handler, second: handler function
using interrupt_handler_t = std::pair< unsigned int, std::function< void() > > ;

// key == interrupt number, mapped data == vector of interrupt handlers
using handler_map_t = std::map< unsigned int, std::vector<interrupt_handler_t> >  ;

// return unique handler number of the added handler
unsigned int add_handler( handler_map_t& handler_map, unsigned int interrupt_number, std::function< void() > handler )
{
    static unsigned int handler_id = 0 ; // unique ids for the handlers
    handler_map[interrupt_number].emplace_back( ++handler_id, std::move(handler) ) ; // increment handler_id for each handler
    return handler_id ;
}

// remove handler with this unique handler number, return true on success
bool remove_handler( handler_map_t& handler_map, unsigned int unique_handler_number )
{
    for( auto&[ int_num, vec ] : handler_map )
        for( auto iter = vec.begin() ; iter != vec.end() ; ++iter )
           if( iter->first == unique_handler_number )
            {
                vec.erase(iter) ;
                return true ;
            }

    return false ; // no handler with this unique_handler_number 
}

// return number of handlers called
std::size_t try_handle_interrupt( unsigned int interrupt_number, const handler_map_t& handler_map )
{
    const auto iter = handler_map.find(interrupt_number) ;
    if( iter != handler_map.end() ) // if an entry is found
    {
        // call the handlers stored in the vector
        for( auto& handler : iter->second ) handler.second() ;
        return iter->second.size() ; // number of handlers
    }
    else return 0 ;
}

int main()
{
    struct A
    {
        void handle_interrupt_1() { std::cout << "A::handler for interrupt #1 A object: " << this << '\n' ; }
        void handle_interrupt_2() { std::cout << "A::handler for interrupt #2 A object: " << this << '\n' ; }
    };

    struct B
    {
        void handle_interrupt_1() { std::cout << "B::handler for interrupt #1 B object: " << this << '\n' ; }
        void handle_interrupt_2( int extra_arg )
        { std::cout << "B::handler for interrupt #2 B object: " << this << " extra_arg: " << extra_arg << '\n' ; }
    };

    A a1, a2 ;
    B b ;

    handler_map_t handler_map ;

    // add handlers for interrupt #1
    [[maybe_unused]] const auto h11 = add_handler( handler_map, 1, std::bind( &A::handle_interrupt_1, std::ref(a1) ) ) ;
    const auto h12 = add_handler( handler_map, 1, std::bind( &B::handle_interrupt_1, std::ref(b) ) ) ;

    // add handlers for interrupt #2
    [[maybe_unused]] const auto h21 = add_handler( handler_map, 2, std::bind( &A::handle_interrupt_2, std::ref(a1) ) ) ;
    const auto h22 = add_handler( handler_map, 2, std::bind( &A::handle_interrupt_2, std::ref(a2) ) ) ;
    const auto h23 = add_handler( handler_map, 2, std::bind( &B::handle_interrupt_2, std::ref(b), 23 /* pass 23 as extra_arg */ ) ) ;
    
    std::cout << "\n*** added five handlers ***\n\n" ;

    // try handling some interrupts
    for( unsigned int interrupt_number = 1 ; interrupt_number < 4 ; ++interrupt_number )
    {
        std::cout << "interrupt_number #" << interrupt_number << '\n' ;
        if( const auto n = try_handle_interrupt( interrupt_number, handler_map ) )
            std::cout << "handled: " << n << " handlers were called\n" ;
        else std::cout << "unhandled: no entry in the map for this interrupt in the map\n" ;
        std::cout << "\n---------------------------\n" ;
    }

    // remove some handlers
    remove_handler( handler_map, h12 ) ;
    remove_handler( handler_map, h22 ) ;
    remove_handler( handler_map, h23 ) ;
    std::cout << "\n*** removed three of the handlers ***\n\n" ;

    // and try handling the interrupts again
    for( unsigned int interrupt_number = 1 ; interrupt_number < 4 ; ++interrupt_number )
    {
        std::cout << "interrupt_number #" << interrupt_number << '\n' ;
        if( const auto n = try_handle_interrupt( interrupt_number, handler_map ) )
            std::cout << "handled: " << n << " handlers were called\n" ;
        else std::cout << "unhandled: no entry in the map for this interrupt in the map\n" ;
        std::cout << "\n---------------------------\n" ;
    }
}

http://coliru.stacked-crooked.com/a/a7b45538367a3d3e
Topic archived. No new replies allowed.