views:

289

answers:

5

I think the problem is pretty common. You have some input string, and have to call a function depending on the content of the string. Something like a switch() for strings. Think of command line options.

Currently I am using:

using std::string;

void Myclass::dispatch(string cmd, string args) {
    if (cmd == "foo")
        cmd_foo(args);
    else if (cmd == "bar")
        cmd_bar(args);
    else if ...
        ...
    else
       cmd_default(args);
}

void Myclass::cmd_foo(string args) {
...
}

void Myclass::cmd_bar(string args) {
...
}

and in the header

class Myclass {
    void cmd_bar(string args);
    void cmd_foo(string args);
}

So every foo and bar I have to repeat four (4!) times. I know I can feed the function pointers and strings to an static array before and do the dispatching in a loop, saving some if...else lines. But is there some macro trickery (or preprocessor abuse, depending on the POV), which makes is possible to somehow define the function and at the same time have it update the array automagically? So I would have to write it only twice, or possibly once if used inline?

I am looking for a solution in C or C++.

+7  A: 

It sounds like you're looking for the Command pattern

Something like this:

Create a map like this

std::map<std::string, Command*> myMap;

then just use your key to execute the command like this....

std::map<std::string, Command*>::iterator it = myMap.find(str);
if( it != myMap.end() ) {
    it->second->execute()
}

To register your commands you just do this

myMap["foo"] = new CommandFoo("someArgument");
myMap["bar"] = new CommandBar("anotherArgument");
Glen
No. It is probably misleading that I have used functions called "cmd_*" but they could be something different, the need not to represent commands. Additional the dispatcher doesn't need to be bound in an object.
drhirsch
@drhirsch. They don't have to be actual commands. That's just the name of the pattern. Basically it's a way of executing some pre-defined code based on a input
Glen
I would think that the solution Neil and Glen are suggesting is the right answer. It doesn't include self-registration, but I'm not sure there's a clean way to do it in C++. In Java or C#, it would be very doable, though.
Steven Sudit
@Steven You could probably do self registration using some form of static initialisation in each of the command classes. Though then you'd probably need to make your std::map a singleton. Like you say, it's not very clean
Glen
It doesn't need to be clean, note that I explicitly asekd for preprocessor abuse.But this solution looks very interesting, it does give me some new ideas. But now I need the CommandFoo and CommandBar class declarations and constructors, so I am repeating Foo and Bar four times again.
drhirsch
Well, cut the constructor, so I am down to three. +1 for the improvement.
drhirsch
@Glen: Right, but then you'd have potential issues involving the order of static member initialization among modules. It can get a bit ugly.
Steven Sudit
+2  A: 

as alternative to the Command pattern you can build an hashtable of string -> function pointers:

typedef void (*cmd)(string);
dfa
This is not an actual alternative to the _pattern_: it's the same concept, differently implemented.
xtofl
+1  A: 

The ugly macro solution, which you kind-of asked for. Note that it doesn't automatically register, but it does keep some things synchronized, and also will cause compile errors if you only add to mappings, and not the function in the source file.

Mappings.h:

// Note: no fileguard
// The first is  the text string of the command, 
// the second is the function to be called, 
// the third is the description.
UGLY_SUCKER( "foo", cmd_foo, "Utilize foo." );
UGLY_SUCKER( "bar", cmd_bar, "Turn on bar." );

Parser.h:

class Myclass {
...
protected:
    // The command functions
    #define UGLY_SUCKER( a, b, c ) void b( args )
    #include Mappings.h
    #undef UGLY_SUCKER
};

Parser.cpp:

void Myclass::dispatch(string cmd, string args) {
    if (cmd == "")
        // handle empty case
#define UGLY_SUCKER( a, b, c ) else if (cmd == a) b( args )
#include Mappings.h
#undef UGLY_SUCKER
    else
       cmd_default(args);
}

void Myclass::printOptions() {
#define UGLY_SUCKER( a, b, c ) std::cout << a << \t << c << std::endl
#include Mappings.h
#undef UGLY_SUCKER
}

void Myclass::cmd_foo(string args) {
...
}
Caleb Huitt - cjhuitt
Well, granted, it's ugly, but it seems cut the repeatitions down to two. But an #include in the middle of the code is really ugly. +1
drhirsch
+1, for the same reason. Also, assuming that each command "foo" results in calling cmd_foo, with a bit of stringification you could avoid the repetition in the parameters as well.
Andrew Y
Yes, I am thinking along the lines of the "paste" operator to.
drhirsch
+5  A: 

The basic solution, per my link in the question comment, is to map a string to a function call of some sort.

To actually register the string -> function pointer/functor pair:

Firstly, have a singleton (shock! horror!) dispatcher object. Let's call it TheDispatcher - it's a wrapper for a map<string,Func>, where Func is your function pointer or functor type.

Then, have a register class:

struct Register {
   Register( comst string & s, Func f ) {
      TheDispatcher.Add( s, f );
   }
};

Now in your individual compilation units you create static objects (shock! horror!):

Register r1_( "hello", DoSayHello );

These objects will be created (assuming the code is not in a static library) and will automatically register with TheDispatcher.

And at run-time, you look up strings in TheDispatcher and execute the associated function/functor.

anon
+1  A: 

You'll have to at least define the functions and add them to some registry. (If they are to be non-inline member functions of some class, you'll also have to declare them.) Other than some domain-specific language generating the actual code (like cjhuitt's macro hackery), I see no way around mentioning these functions two (or three) times.

sbi
Yes, I think i got that finally into my thick skull.
drhirsch