1

I want to know, if it is possible to build a name to function table in c++, something like map<string, function handle>. But

  1. those functions have different signatures. I can assume they have same return type as void.

  2. I was thinking define something like,

    struct ftable { std::string name; void (void* pfun)(); // this is only for one function signature };

But how to make it work for different types of function?

I asked a related question here. In that question, I try to store functions into some container, but I realize that I can not store function with placeholder (see following code). Is this possible with c++? Thanks!

template <typename F, typename ... Args>
std::function<void()> MapFun (F const & f, Args const & ... args)
    { return [=]{ f(args...); }; }

void userFun1 (int i1, int i2)
{ std::cout << "uf1, " << i1 << ", " << i2 << std::endl; }

int main ()
{
   auto l1 = MapFun(userFun1, 1, 2);
   std::unordered_map<std::string, std::function<void()>> func_map;
   func_map["userFun1"] = std::bind(l1); // this is okay;
   //func_map["userFun1"] = std::bind(userFun1, std::placeholders::_1, std::placeholders::_2); // this is wrong;
   //auto lll1 = MapFun(userFun1, std::placeholders::_1,std::placeholders::_2); // also wrong.
}

UPDATE: What I want is like plugin development. I have a server and a client. I can write my function on client, and build it as a shared library, then send that shared library to server as well as a signal telling server to load it. I hope server program can load function with different signatures from shared library without restart or rebuild. Is this possible, or I have to fix the signature and restrict all shared functions with that?

J.Yang
  • 49
  • 7
  • 5
    Why do you think you need this? Sounds like and XY problem to me. What is the underlying problem you are trying to solve? – super Nov 15 '18 at 05:11
  • 7
    After you retrieve a function from the map, how will you know what its signature is? – Raymond Chen Nov 15 '18 at 05:13
  • @RaymondChen I think you are right. C++ is a language needs to know type at compilation. – J.Yang Nov 15 '18 at 14:30
  • I have updated my question, @super, thanks for your comment – J.Yang Nov 15 '18 at 14:31
  • If you know the signature at compile time (e.g. "The item named `bob` is always a `void(*)(int,int)`") then you can cast a plain function pointer to `void(*)()` for storage, then cast it to `void(*)(int,int)` after retrieving. Function pointers of different signature can be interconverted without loss of fidelity, but you have to cast back to the original type before using them to call the function. If you cast to the wrong signature and then try to call the function, the result is undefined (i.e., "bad"). – Raymond Chen Nov 15 '18 at 15:37
  • Your OS already implements function tables for dynamically loaded libraries. Perhaps you want to check out how `dlsym` or `GetFunctionAddress` are used. Do you want something substantially different from these? In what ways? – n. m. could be an AI Nov 16 '18 at 07:45
  • @n.m. Yes, I use dlopen and dlsym. But I think I still need to know the return and input type then I can write a pointer to get function handle. – J.Yang Nov 16 '18 at 14:41
  • *Do you want something substantially different from these? In what ways?* Let me ask this question again in a more elaborate manner. You are already doing `dlsym` followed by a cast. Do you want to retrieve your function object and then use it *without* a cast and *without* knowing what the type really is? If this is not the case, how is what you re doing different from `dlsym`? – n. m. could be an AI Nov 16 '18 at 14:49
  • @n.m. I use dlsym with compromise. The function in shared library can't have arbitrary signatures. Instead, I want: write a arbitrary function and build it as a shared library, then submit it to a server application (with inputs) and ask server application to run it. The server application does not know what kind of function I wrote, but I guarantee the inputs will match the function signature. So I want a general interface in server application, it picks the function from the shared library, feed with inputs transferred from me and believe those inputs are legal. – J.Yang Nov 16 '18 at 15:37
  • You are changing your story. "write a arbitrary function and build it as a shared library, then submit it to a server application (with inputs) and ask server application to run it." This is entirely different from looking up functions in a table. How do you imagine your system to work? Forget about different signatures, imagine all your functions are `void(void)`. How do you submit such functions to a server application to run? I understand how you can *call* a function. But submit it to another program? – n. m. could be an AI Nov 16 '18 at 17:15
  • @J.Yang in the server process, the "inputs" have a type. Maybe it's std::any, maybe it's void*, maybe it's char[N]. Whatever it is, they have a type. So you are calling a function with arguments of particular type, i.e. you are only supporting a fixed function signature. This is no more general than what you have now. – Jeff Garrett Nov 16 '18 at 20:05
  • @n.m. You need a client part and a server part, and a designed protocol. When you "submit" a function, you build a shared library and send to server. Server application is always online and listening, when it received the message, it goes to someplace to load that library and call the function – J.Yang Nov 16 '18 at 20:53
  • @JeffGarrett I was looking for a way to store the 'type' and restore it in runtime. but I now feel that is not feasible – J.Yang Nov 16 '18 at 20:57
  • OK suppose you somehow convince your server to take an untrusted shared library of unknown architecture and run it. Good luck with that. The question remains: how does the server know what arguments to send to your function? If the answer to this question is "I will send the function and the arguments togetger", then you just tell it to call a `function` *closed over* your arguments, whatever they are. If the answer is something else, then what it is? – n. m. could be an AI Nov 16 '18 at 22:00

2 Answers2

1

It is possible but at some point you will need to know the return and parameter types.

You can use a type-erasure class to hide the return/parameter types and then store the type-erasure class. A naive implementation like this would suffice,

#include <iostream>
#include <functional>
#include <unordered_map>

class MyLambda {    

    public:

        MyLambda() = default;

        virtual ~MyLambda() = default;

};

template <typename T>
class HiddenLambda : public MyLambda {
    static_assert(std::integral_constant<T, false>::value, "Template parameter needs to be of function type.");
};

template <typename Ret, typename... Args>
class HiddenLambda<Ret(Args...)> : public MyLambda {

        public:

            HiddenLambda(std::function<Ret(Args...)> _fun) :  fun_(_fun) { }

            Ret operator() (Args... args) {return fun_(args...);}

        private:

        std::function<Ret(Args...)> fun_;
};

int main() {

    std::unordered_map<std::string, std::shared_ptr<MyLambda>> my_lambdas;

    my_lambdas.insert(std::make_pair("fun1", std::shared_ptr<MyLambda>( 
        new HiddenLambda<size_t(std::string)>(
            [](std::string s) { return s.size(); } // <- lambda you want to store
            )
        )
    ));

    my_lambdas.insert(std::make_pair("fun2", std::shared_ptr<MyLambda>( 
        new HiddenLambda<int(int)>(
            [](int x) { return x * 5; } // <- lambda you want to store
            )
        )
    ));

    auto it = my_lambdas.find("fun1");

    /* Get the function we want! */
    std::shared_ptr<MyLambda> a_lam = it->second;

    /* Need to know the types to actually use it though */
    HiddenLambda<size_t(std::string)>& actual_lam = dynamic_cast<HiddenLambda<size_t(std::string)>&>(*a_lam);

    std::cout << actual_lam("how long is this string?") << "\n";
}

If this is what we really need to do, I suggest looking up various methods for type erasure.

I think the problem you are trying to solve probably has an easier solution. If you could give more details perhaps we could help?

EDIT

More relevant example to your provided code...

/* include above classes and includes */
void myfunc(int x, int y) {
    std::cout << "uf1, " << x << ", " << y << std::endl;
}

int main() {
    std::unordered_map<std::string, std::shared_ptr<MyLambda>> func_map;

    func_map["userFun1"] = std::shared_ptr<MyLambda>(
        new HiddenLambda<void(int, int)>( &myfunc ) // <- no args binded, notice the type = void(int,int)
    ); 

    func_map["userFun2"] = std::shared_ptr<MyLambda>( 
        new HiddenLambda<void(int)>( std::bind(myfunc, std::placeholders::_1,  5) ) // <- one args binded, notice the type = void(int)
    ); 

    func_map["userFun3"] = std::shared_ptr<MyLambda>(
      new HiddenLambda<void()>(  std::bind(myfunc, 1, 2))  // <- two args binded, notice the type = void()
     );

    /* we still need to know the type though,it will be either void(int, int), void(int) or void() */
    HiddenLambda<void(int)>& actual_lam = dynamic_cast<HiddenLambda<void(int)>&>(*func_map["userFun2"]);

    actual_lam(4);

}

EDIT V2

This is more of a guess then anything. I am not sure if you should do this (well definitely not outside of some interesting experimentation) or if it will even work. Here is a possible way if the amount different arguments for different functions is known and finite. This would require a technique called Library Interposing, which I do not know much about.

Firstly in the main program you define this enum and a factory function. The enum will describe every possible parameter range.

enum Types { kVOID, kINT_INT }; // <- verbosely define all the possible ones you would use 

std::pair<Types, std::shared_ptr<MyLambda>> Factory(){
    return 
        {
          kVOID, /* <- some sensible default */
            std::shared_ptr<MyLambda>(
               new HiddenLambda<void()>( []{} ) 
            )
        }; 
}

The shared library will have to provide an overriden factory method. Here is where I think you need the interposer to do so.

std::pair<Types, std::shared_ptr<MyLambda>> Factory(){
    return
     {
        kVOID_INT_INT,
        std::shared_ptr<MyLambda>(
        new HiddenLambda<void(int, int)>( [](int x, int y){ std::cout << (x + y);} ) 
        )
   };
}

Then the main method would look like:

int main() {

    std::unordered_map<std::string, std::pair<Types, std::shared_ptr<MyLambda>>> func_map;

    func_map.insert({"fun1", Factory()});

    auto it = func_map.find("fun1");

    /* always need to be able to deduce they type */
    if (it->second.first == kVOID) {

        CallHidden(*it->second.second); 
    } 

    else if (it->second.first == kINT_INT) { 

        CallHidden<int, int>(*it->second.second, 3, 4); 

    } else {

        /* other ones you have statically typed */

    } 
}
Bar Stool
  • 620
  • 3
  • 13
  • The `static_assert` only covers view data types (what about floating point types? classes/structs?). Better: Just do not implement the template! – Aconcagua Nov 15 '18 at 06:52
  • Thanks for your answer. But it still need to know the type when get the function handle out from the map. It has the same limitation like using `std::vector`. I have updated my question, do you have any other suggestion? – J.Yang Nov 15 '18 at 14:40
  • @J.Yang You always need to know the signature. At some point in your code you will need to give arguments to the function, at that point you need to know the types of all the arguments. I have added an edit that may help. – Bar Stool Nov 16 '18 at 07:08
  • @J.Yang Please let me know if you get it working, I am interested to know what you end up doing. It you are satisfied with this answer, please consider accepting it. – Bar Stool Nov 16 '18 at 07:38
  • @BarStool Thank you for your patient. I need think about your EDIT V2. If this does not work, I think I will fix the signature for the shared function. So it will accept multiple vectors. If I want send a few int inputs, I have to put them all in one vector, and take them out at the beginning of the function. – J.Yang Nov 17 '18 at 00:13
0

If at time of creating object of std::function<void()>, you know function signature then you may be able to do like below.

#include <iostream>
#include <functional>
#include <unordered_map>

void userFun1 (int i1, int i2)
{ 
    std::cout << "uf1, " << i1 << ", " << i2 << std::endl; 
}


int main()
{
    std::unordered_map<std::string, std::function<void()>> func_map;
    func_map["userFun1"] = std::bind(userFun1,1,2);
    func_map["userFun1"]();
    int i1{2},i2{3};
    func_map["userFun1.1"] = std::bind(userFun1,std::ref(i1),std::ref(i2));
    func_map["userFun1.1"]();
    i1 = 3;
    i2 = 4;
    func_map["userFun1.1"]();
    return 0;
}

Live Code

Manthan Tilva
  • 3,135
  • 2
  • 17
  • 41
  • Thanks for your answer. That std::ref sounds like the tricky. I have updated my question, do you think this can be a workaround? – J.Yang Nov 15 '18 at 14:37
  • @J.Yang For your update,I don't think that may be possible. For any plugin system, there is fixed set of function call OR API which are know to both plugin loader and plugin it self. In your case you can create wrapper of your client code which know what are signature of client function and what are value need to pass to client function and call this wrapper function from your server code. Hope this will help! – Manthan Tilva Nov 16 '18 at 05:23