views:

156

answers:

4

I am building a part of a simulator. We are building off of a legacy simulator, but going in different direction, incorporating live bits along side of the simulated bits. The piece I am working on has to, effectively route commands from the central controller to the various bits.

In the legacy code, there is a const array populated with an enumerated type. A command comes in, it is looked up in the table, then shipped off to a switch statement keyed by the enumerated type.

The type enumeration has a choice VALID_BUT_NOT_SIMULATED, which is effectively a no-op from the point of the sim. I need to turn those no-ops into commands to actual other things [new simulated bits| live bits]. The new stuff and the live stuff have different interfaces than the old stuff [which makes me laugh about the shill job that it took to make it all happen, but that is a topic for a different discussion].

I like the array because it is a very apt description of the live thing this chunk is simulating [latching circuits by row and column]. I thought that I would try to replace the enumerated types in the array with pointers to functions and call them directly. This would be in lieu of the lookup+switch.

+3  A: 

Can't be done. However, you could do something sort of like it with a functor. I'd put example code but as I was writing it I realized such a construct would necessarily be quite complicated. You might look at boost::bind for some ideas.

Noah Roberts
+2  A: 

One way to do it, though ugly, is to use a generic table of pointers and cast your function pointers to such generic type (losing information about the arguments' types):

void (*myFunctions[]) () = {
    (void (*)())myFirstFunction,
    (void (*)())mySecondFunction
};

But then you'll have to know, for each of the pointers, what arguments to pass to the corresponding functions. You can extend your table of pointers and make a table of more sophisticated objects which hold some enumeration variable informing about the arguments of a function to which a particular pointer points.

Unfortunately, each time you'll want to use a function from the array, you will need to cast the pointer back to the given type (and you'll have to care not to cast it incorrectly), like so:

((void (*)(int))tab[0])(1);

In order to call myFirstFunction with x = 1.

As I think about it now (after you changed the question), I come to the conclusion that if you have to call the functions differently, there really is no point complicating the whole thing (lookup table), unless there are just a few signatures and many functions available. You need a very consistent calling policy and really few possible signatures to achieve a good-looking solution with a lookup table. Needless to mention what will happen if you need to store pointers to member functions or even worse - virtuals.

Michał Trybus
By this point, you might as well call the functions directly!
Oli Charlesworth
I am just repeating what you said first: that's ugly.
Khnle
I have completely changed my question, which may be bad etiquette, in response to Neil, but I want to say "Thanks," and "Wow, I guess with an answer like that, I get what I deserve."
David
@Oli, sure, as why would you store functions of different arguments and different purposes in an array? The point of a lookup table is that you don't have to worry about the signatures. You just dispatch a task, and you don't care which function will handle it.
Michał Trybus
+1  A: 

Based on your updated question, I'm still not sure how you're going to invoke the functions via the pointer if the functions need different parameter lists.

However, if one parameter list is a subset of the other, could you write thunks to adapt one interface to look like the other? (i.e. discarding irrelevant parameters or synthesising fake parameters).

Oli Charlesworth
At the end, I reckoned that I would have something like: void (*commandArray[])() = {myFirstFunction(1), myFirstFunction(2), mySecondFunction(3,4)};Then I would make the call by: commandArray[commandIndex] where commandIndex was passed in from somewhere else.
David
@David: Based on that, I would suggest going with the thunk solution.
Oli Charlesworth
I appreciate your help. Your feedback cleared my head about the problem. I'll look at thunks and see what I can make out of them.
David
@David: in your 2-function example, just create a helper function with the same prototype as mySecondFunction(), which simply discards the second argument and then calls myFirstFunction(). Now all your functions have the same prototype, so no smelly casting is needed.
Oli Charlesworth
A: 

In your original question, you were describing a scenario that is very common when working with Javascript libraries. In Javascript, libraries often provide a way for "interested parties" to be notified of events that are published by the library, and all that the interested parties need to do is register their own Function object callbacks. In one version of the library, the documentation might say that the callbacks will be passed n arguments (a, b, c, ... in that order), but a future version might want to provide n + m arguments. This change does not have to break existing code because the library can just append the extra m arguments to the argument list, and this works because Javascript uses a caller-cleans-up calling convention (essentially).

In C++, you could do something similar (provide additional arguments to callbacks) as long as you can guarantee that the calling convention that is used by the callbacks and to call the callbacks is a caller-cleans-up calling convention such as the C calling convention for the x86 architecture:

#include <cstdlib>
#include <iostream>
#include <vector>

extern "C" void old_api_callback_in_old_archive(int x) {
    std::cout << "`old_api_callback_in_old_archive` was called with x = " << x << std::endl;
}

extern "C" void new_api_callback(int x, int otherInfo) {
    std::cout << "`new_api_callback` was called with x = " << x
        << ", otherInfo = " << otherInfo << std::endl;
}

extern "C" {
    typedef void (*callback_type)(int, int);
}

int main()
{
    std::vector<callback_type> callbacks;
    callbacks.push_back(&new_api_callback);
    callbacks.push_back(reinterpret_cast<callback_type>(&old_api_callback_in_old_archive));

    std::vector<callback_type>::iterator it;
    for (it = callbacks.begin(); it != callbacks.end(); ++it) {
        (*it)(7, -8);
    }

    return EXIT_SUCCESS;
}
Daniel Trebbien