5

I am trying to create a unordered map of std::functions. Where a key is a string where you will look up the function you want to call and the function is the value.

I have written a small program:

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

void func1()
{
  std::cout << "Function1 has been called." << std::endl;
}

int doMaths(int a)
{
  return a + 10;
}

int main()
{
  std::unordered_map<std::string,std::function<void()>> myMap;

  myMap["func1"] = func1;

}

This compiles just fine, and I can call the function (however I am not sure if this is the correct way of doing it) by placing:

auto mapIter = myMap.find("func1");
auto mapVal = mapIter->second;
mapVal();

That then calls the function, however I think this is at the cost of creating a new copy of the function? Please correct me if I am wrong about that.

However, if I try to do: myMap["doMaths"] = doMaths; I get a compiler error since the value in myMap is std::function<void()>> and not std::function<int(int)>>. I did get this to compile when I did: myMap["doMaths"] = std::bind(doMaths,int()); However I do not know what that actually does. And when I try to call it in the same manner as func1, I get a compiler error.

So I guess I have two questions:

How do I create an unordered_map that will take any type of function for it's value? And how do I call the function within the map without having to make a copy of the function?

Sailanarmo
  • 1,139
  • 15
  • 41
  • 5
    How do you expect to use it? How do you know that `myMap[name]` should be called with zero, one or more parameters ? – Jarod42 Apr 04 '19 at 16:28
  • 1
    You can use `std::unordered_map`. – eerorika Apr 04 '19 at 16:36
  • I guess I would have knowledge of which function needs to be called. I am trying to find the words to explain myself correctly. Say I have a function that needs to be updated within a class and a UI. And both of them have a function called `updateValue(int)`. Instead of calling `ui->updateValue(int)` and `class->updateValue(int)` they are wrapped in another class that will have this function that will update both of their values. I don't know if I explained that clearly enough. @Jarod42 – Sailanarmo Apr 04 '19 at 16:38
  • @eerorika can you give an example? – Sailanarmo Apr 04 '19 at 16:38
  • 1
    @Sailanarmo example: http://coliru.stacked-crooked.com/a/b1415747073e8ba9 – eerorika Apr 04 '19 at 16:46
  • @moderator who marked as duplicate. This doesn't really answer my questions. Those links that are provided are still hard coded arguments and I am trying to take any arguments without having to hard code them. I am also trying to call them in a way that doesn't necessarily require coding something called a callback (if this is impossible then please let me know.) – Sailanarmo Apr 04 '19 at 16:48
  • @eerorika How would I call the function `doMaths`? Like this will get it to compile, but how would I end up calling the function. – Sailanarmo Apr 04 '19 at 16:49
  • 1
    @Sailanarmo `audo mapIter = myMap.find("doMaths"); int result = std::any_cast (mapIter->second) (42);`, see: https://wandbox.org/permlink/dOMqAhQeuv6d5fuC – Paul Sanders Apr 04 '19 at 16:51
  • @eerorika or Paul, will one of you please put an answer and I will select it as the correct answer? This answered my question perfectly. – Sailanarmo Apr 04 '19 at 16:59
  • @PaulSanders I asked a moderator to open it again and it should be opened now? I have refreshed the page a couple of times and the duplicate has been removed. – Sailanarmo Apr 04 '19 at 17:09

3 Answers3

7

As eerorika says, you can do this with a map of std::any. Here is some example code, showing also how you call the function pointers stored in the map:

#include <iostream>
#include <unordered_map>
#include <string>
#include <any>

void func1()
{
    std::cout << "Function1 has been called.\n";
}

int doMaths(int a)
{
    std::cout << "doMaths has been called, a = " << a << "\n";
    return a + 10;
}

int main()
{
    std::unordered_map<std::string,std::any> myMap;

    myMap["func1"] = func1;
    auto mapIter = myMap.find("func1");
    std::any_cast <void (*) ()> (mapIter->second) ();
    
    myMap["doMaths"] = doMaths;
    mapIter = myMap.find("doMaths");
    int result = std::any_cast <int (*) (int)> (mapIter->second) (5);
    std::cout << result;
}

Live demo

std::any_cast will throw a std::bad_any_cast exception at runtime if the types (or, in this case, function signatures) don't match.

Please note: std::any requires C++17, see: https://en.cppreference.com/w/cpp/utility/any

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
2

If you know that the type of your function is std::function<void(void)>, put it in a std::unordered_map<std::string,std::function<void()>>, and look it up there.

If you know that the type of your function is std::function<int(double, char)>, put it in a std::unordered_map<std::string,std::function<int(double, char)>>, and look it up there.

If you don't know the type of your function, you cannot use it, so it is also useless to store it in a map.

If you have more than one type of functions, also have more than one map. A variable template (or a function template with a static map variable) can help with having any number of such maps without duplicating any code. Of course this way you only can have a global map. A class that can manage such a collection of maps could be a bit more involved, but only a bit.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • @nm If you read the OP's comments above, I think he's happy to 'know' the function signature for the function he plans to call after retrieving it from the map (for good or ill). – Paul Sanders Apr 04 '19 at 17:41
1

Like Paul said, but with std::function syntax. You can use this to put functions with any signature in your map, even lambdas :D

#include <vector>
#include <iostream>
#include <functional>
#include <map>
#include <any>

int doMaths(int a)
{
    std::cout << "doMaths has been called, a = " << a << "\n";
    return a + 10;
}

int main()
{
  std::map<std::string, std::any> map;

  map["foo"] = std::function<int(int)>([](int a) { return a * 2; });

  map["bar"] = std::function<int(int, int)>([](int a, int b) { return a + b; });

  map["maths"] = std::function<int(int)>(doMaths);

  int a = std::any_cast<std::function<int(int)>>(map["foo"])(4);

  int b = std::any_cast<std::function<int(int, int)>>(map["bar"])(4, 5);

  int c = std::any_cast<std::function<int(int)>>(map["maths"])(5);

  std::cout << a << " " << b << " " << c <<std::endl;
}

Just be careful when you cast, you'll need to know the correct signature and return type.

Philip Nelson
  • 1,027
  • 12
  • 28