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.

8
  • 2
    What's the overall goal? In most cases where people use switches, polymorphism is what they should be using. Commented Dec 17, 2009 at 21:54
  • 1
    +1 replacing switches with table lookups is something I like to do in dynamic languages. Commented Dec 17, 2009 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. Commented Dec 17, 2009 at 21:56
  • 1
    Do the methods all have the same type signature? Commented Dec 17, 2009 at 21:58
  • 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. Commented May 28, 2016 at 20:31

4 Answers 4

49

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;
}
Sign up to request clarification or add additional context in comments.

2 Comments

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.
Oh I missed the scope resolution Class::<function> and it was giving me 'cannot convert arguments' errors. This helped me :)
5

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{});
}

13 Comments

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<std::string, decltype(l)> m;
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{});?
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{});.
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.
|
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.

4 Comments

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.
@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.
In that case I don't see the advantage of your approach over named functions.
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.
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;    
};      

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.