34

I need to implement an std::map with <std::string, fn_ptr> pairs. The function pointers are pointers to methods of the same class that owns the map. The idea is to have direct access to the methods instead of implementing a switch or an equivalent.

( I am using std::string as keys for the map )

I'm quite new to C++, so could anyone post some pseudo-code or link that talks about implementing a map with function pointers? ( pointers to methods owned by the same class that owns the map )

If you think there's a better approach to my problem, suggestions are also welcome.

skypjack
  • 49,335
  • 19
  • 95
  • 187
Goles
  • 11,599
  • 22
  • 79
  • 140
  • 2
    What's the overall goal? In most cases where people use switches, polymorphism is what they should be using. – outis Dec 17 '09 at 21:54
  • 1
    +1 replacing switches with table lookups is something I like to do in dynamic languages. – just somebody Dec 17 '09 at 21:55
  • I have a factory class, that will have several constructor methods returning classes of type A. The idea is to do something like A *aClass = Factory->newA("key"); . Then the factory would use the "key" to invoke the correct method to construct an A class, and return it accordingly. – Goles Dec 17 '09 at 21:56
  • 1
    Do the methods all have the same type signature? – outis Dec 17 '09 at 21:58
  • Yes, I think they all receive the same parameters and return the same class. – Goles Dec 17 '09 at 21:59
  • You probably want to use a template for that if possible. – rlbond Dec 17 '09 at 21:59
  • @rlbond: I thought about it, but I'm not really sure how to do it. If you think it's the best approach, I would love to see some example code – Goles Dec 17 '09 at 22:00
  • 1
    The OP's use case may be achieved in other ways, but I have a parameters class that I want to expose to a utility, so giving a map of strings (function setter/getter names) to the actual setters/getters is about the best I can think of. This question and the accepted answer seem to be the solution for me. – sage May 28 '16 at 20:31

4 Answers4

48

This is about the simplest I can come up with. Note no error checking, and the map could probably usefully be made static.

#include <map>
#include <iostream>
#include <string>
using namespace std;

struct A {
    typedef int (A::*MFP)(int);
    std::map <string, MFP> fmap;

    int f( int x ) { return x + 1; }
    int g( int x ) { return x + 2; }


    A() {
        fmap.insert( std::make_pair( "f", &A::f ));
        fmap.insert( std::make_pair( "g", &A::g ));
    }

    int Call( const string & s, int x ) {
        MFP fp = fmap[s];
        return (this->*fp)(x);
    }
};

int main() {
    A a;
    cout << a.Call( "f", 0 ) << endl;
    cout << a.Call( "g", 0 ) << endl;
}
  • Worked nicely, thanks!, this thread became pretty useful, since the template Implementation posted by @outis, is really good too. Will mark this as the answer to the specific question, but be sure to read that one too. – Goles Dec 17 '09 at 22:58
  • Oh I missed the scope resolution Class:: and it was giving me 'cannot convert arguments' errors. This helped me :) – SajithP Jul 19 '17 at 02:50
4

A template implementation could look like:

class Factory {
public:
    enum which {
        foo, bar, baz
    };

    template<which w>
    A* newA(...);
    ...
};
template<Factory::which w>
A* Factory::newA(...) {
    /* default implementation */
    throw invalid_argument();
}
template<>
A* Factory::newA<Factory::foo>(...) {
    /* specialization for a 'foo' style A */
    ...
}
....

This requires that the value used to determine which newA is called be known at compile time. You could potentially use a const char * as the template parameter, but it's not guaranteed to work on all compilers.

Yet another option is to create helper factories, one for each factory creation method, and store those in the map. This isn't a huge advantage over storing method pointers, but does let you define a default creation method and simplifies fetching things from the map (there's no need to check that the key exists, because you'll get a default factory). On the downside, an entry for each unknown key would be added to the map.

Also, if you use an enum rather than a string for the key type, you shouldn't need to worry about checking whether a key exists in the map. While it's possible for someone to pass an invalid enum key to newA, they'd have to explicitly cast the argument, which means they're not going to do it by accident. I'm having a hard time imagining a case where someone would purposefully cause a crash in newA; the potential scenarios involve security, but an application programmer could crash the app without using your class.

outis
  • 75,655
  • 22
  • 151
  • 221
  • Am I missing something or does tnot he code above only work for compile-time values of th enumerated type? If I have an enumeration variable of type "which", I can't create new A thingies depending on the contents of the variable. –  Dec 17 '09 at 23:27
  • @Neil: You're missing nothing. That's the big limitation of the template approach. It may not be suitable for Mr. Gando's purposes, but it's worth considering. – outis Dec 17 '09 at 23:35
  • In that case I don't see the advantage of your approach over named functions. –  Dec 17 '09 at 23:49
  • 1
    Using type-correctness to ensure valid method invocation is a good thing, because everything but the invocation is handled at compile time. Advantages: O(1) dispatch time at runtime and no real error checking needs to be done (the default `Factory::newA` shouldn't actually be callable, unless the coder forgets to implement a specialization, which should be caught by a unit test). The dynamic approach of doing everything at runtime has it's own advantages; I mostly posted the sample because Mr. Gando asked to see code for rlbond's suggestion. – outis Dec 18 '09 at 00:36
4

Since C++14, we can use a generic lambda to get rid easily of pointers to member methods.
It follows a minimal, working example of a forward function made up with a generic lambda function:

#include<utility>
#include<map>
#include<string>
#include<iostream>

struct SomeClass { };
struct SomeOtherClass { };

struct Test {
    void test(SomeClass) { std::cout << "SomeClass" << std::endl; }
    void test(SomeOtherClass) { std::cout << "SomeOtherClass" << std::endl; }
};

int main() {
    Test test;

    auto l = [&test](auto c){ test.test(c); };
    std::map<std::string, decltype(l)> m;

    m.emplace("foo", l);
    m.emplace("bar", l);

    m.at("foo")(SomeClass{});
    m.at("bar")(SomeOtherClass{});
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • This is great and solves my problem (which has a unique topology than the others). Can you please explain how it works? In particular `auto l = [&test](auto c){ test.test(c); }; std::map m;` – Klik Mar 29 '17 at 19:30
  • Wait a second. Calling `m.at("foo")(SomeClass{}); m.at("foo")(SomeOtherClass{});` gives the same result. What's the difference between this and just calling `test.test(SomeClass{}); test.test(SomeOtherClass{});`? – Klik Mar 29 '17 at 19:48
  • [They give different results indeed](https://wandbox.org/permlink/8MEU3gr1DKbxz4Ms). – skypjack Mar 29 '17 at 21:46
  • Let me rephrase, calling `m.at("foo")(SomeClass{});` will give me the same result as calling `m.at("bar")(SomeClass{});`. Thus, calling `m.at("foo")(SomeClass{}); m.at("foo")(SomeOtherClass{});` gives me the same result as your lines of `m.at("foo")(SomeClass{}); m.at("bar")(SomeOtherClass{});`. They are also equivalent to calling `test.test(SomeClass{}); test.test(SomeOtherClass{});`. – Klik Mar 29 '17 at 22:09
  • It seems that `m.at("foo")` has the exact same usage as `m.at("bar")` and therefore I don't see how to make this useful, compared to using the standard overloaded functions. I was very interested in this code piece because I would like to map strings to functions (with different names) that take a single parameter of a unique type. – Klik Mar 29 '17 at 22:12
  • They give the same result for they use the same generic lambda that accepts everything and dispatch the call to `Test`. That's a trick to be able to construct such a map, but it simply hides the fact that using a string-to-function map in C++ doesn't make much sense. – skypjack Mar 30 '17 at 05:38
  • I think using a string-to-function map in C++ makes a lot of sense depending on the problem. For example, I am working on a robot which uses ROS. I'm not sure if you're familiar so forgive me for using jargon. I have more than a few dozen topics to publish to and each is associated with a particular message type. I would really love to map the string that represents the topic to the message type that should be created. The closest answer I've found so far is this one: http://stackoverflow.com/a/33837343/1406888. – Klik Mar 30 '17 at 05:42
  • @Klik Lookups in a map by string should be forbidden by law!! :-D ... Btw C++ has a lot of interesting features that one usually uses in place of a string-to-function map, really. As an example, if your strings are known at compile-time time as it seems to be, you can do something different. – skypjack Mar 30 '17 at 05:49
  • I've been looking into this since the morning, if you have any suggestions I would be delighted to hear them. There's a lot of repetition in my code that I want to get rid of. It all boils down to making something that can go from string to constructor function. – Klik Mar 30 '17 at 05:52
  • @Klik Can you create a minimal example that reproduces the problem? It would be easier to work on that. – skypjack Mar 30 '17 at 05:56
  • Hope this looks nice... `void SendMessage( const std::string& topic_name, const nav_msgs::OccupancyGrid& msg) { if (topic_name == topics::costmap_topic) SectorMapCallback(msg); } void SendMessage(const rosbag::MessageInstance& m) { if (topic_name == topics::costmap_topic) { SendMessage(topic_name, *m.instantiate()); } }` – Klik Mar 30 '17 at 06:07
  • Given the topic string I know precisely what function needs to be called and how the message needs to be instantiated. However, I need to use a bunch of if statements and make changes to different locations in the code to add something new. It's not nice. – Klik Mar 30 '17 at 06:10
  • There is no need to emplace "bar" it is the same function. m.at("foo")(SomeClass{}); m.at("bar")(SomeOtherClass{}); is the same thing as m.at("foo")(SomeClass{}); m.at("foo")(SomeOtherClass{}); – Adam Freeman Mar 08 '19 at 19:07
2

Another option is to use delegates as oppose to function pointers. This delegate implementation is pretty fast, supports polymorphisms, and plays well with stl containers. You could have something like:

class MyClass {
public:
    // defines
    typedef fastdelegate::FastDelegate2<int, int, int> MyDelegate;
    typedef std::map<std::string, MyDelegate> MyMap;

    // populate your map of delegates
    MyClass() {
        _myMap["plus"] = fastdelegate::MakeDelegate(this, &Plus);
        _myMap["minus"] = fastdelegate::MakeDelegate(this, &Minus);
    }

    bool Do(const std::string& operation, int a, int b, int& res){
        MyMap::const_iterator it = _myMap.find(operation);
        if (it != _myMap.end()){
            res = it.second(a,b);
            return true;
        }

        return false; 
    }
private:
    int Plus (int a, int b) { return a+b; }
    int Minus(int a, int b) { return a-b; }
    MyMap _myMap;    
};      
axs6791
  • 687
  • 1
  • 6
  • 12