1

I am trying to create a interface between user defined function and data. Let's say I need to create a function called MapFun(), input of MapFun() includes user defined function (UDF) handle and UDF inputs.

void userFun1(Data data, int in1, int in2){
    // user defined function 1;
}

void userFun2(Data data, int in1, int in2, std::string s){
    // user defined function 2;
}

// ...
// apply user function 1 on data
MapFun(@userFun1, data, 3, 4);
// apply user function 2 on data
MapFun(@userFun2, data, 1, 2, "algorithm");

User will write userFun and apply it with MapFun(). So, how to design MapFun()? User function may have different inputs and the signature can't be predicted. In addition, MapFun() won't evaluate userFun immediately, instead, it stores all userFun and do a lazy evaluation.

Any suggestions are greatly appreciated.

max66
  • 65,235
  • 10
  • 71
  • 111
J.Yang
  • 49
  • 7
  • If you can't predict the signature of `userFun`, how will you know what parameters to give it? – Daniel H Nov 13 '18 at 01:13
  • Unfortunately, `userFun` will be defined by users. `MapFun` can't know what parameters will be used for `userFun`. I searched, and feel it might be similar to the question here: https://stackoverflow.com/questions/45715219/store-functions-with-different-signatures-in-a-map/45718187 – J.Yang Nov 13 '18 at 01:19
  • Do the user functions all have the same return type (possibly `void`)? – aschepler Nov 13 '18 at 01:22
  • Oh, I understand now. I misread your code snippet and was a bit confused. It sounds like you want [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke). – Daniel H Nov 13 '18 at 01:28
  • @aschepler I think I can assume all user functions have void return type. – J.Yang Nov 13 '18 at 01:35
  • necessarily C++11 or also C++14/C++17? – max66 Nov 13 '18 at 01:37
  • @DanielH I checked `std::invoke` a little bit, it sounds like it is used to invoke a defined function. The problem here is I write `MapFun` but user write `userFun`, and `MapFun` does not know what `userFun` looks like. Is it possible to write a code compatible to all kinds of `userFun`? – J.Yang Nov 13 '18 at 01:45
  • @max66 can be any of them. – J.Yang Nov 13 '18 at 01:46

3 Answers3

3

User function may have different inputs and the signature can't be predicted.

It seems a typical works for variadic templates

In addition, MapFun() won't evaluate userFun immediately, instead, it stores all userFun and do a lazy evaluation.

Not sure to understand but I suppose you can obtain what do you want using std::bind() or, maybe better, with lambda functions.

I propose the following C++14 variadic template MapFun() function that return a lambda function that capture both user-function and argument. That function can be executed later.

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

The following is a full working example

#include <iostream>

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

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

void userFun2 (int i1, int i2, std::string const & s)
 { std::cout << "uf2, " << i1 << ", " << i2 << ", " << s << std::endl; }

int main ()
 {
   auto l1 = MapFun(userFun1, 1, 2);
   auto l2 = MapFun(userFun2, 3, 4, "five");

   l2();
   l1();
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Capture by reference is dangerous here. Even the example `MapFun(userFun1, 1, 2)` is likely to blow up, because the lambda object contains references to `const int&` values from temporaries initialized to `1` and `2`, and those referenced objects no longer exist when the functions are called. – aschepler Nov 13 '18 at 01:58
  • @aschepler - yes, it's a incredibly stupid mistake; corrected; thanks. – max66 Nov 13 '18 at 02:01
  • Thank you for your help! It seems that this and @aschepler 's suggestion are what I am looking for. – J.Yang Nov 13 '18 at 02:19
1

If I understand right, you just want to store a function and a set of arguments so that they can be called later. This is exactly what std::bind does (anyway, the simplest thing it does). In fact, you could possibly just use std::bind directly in place of your MapFun:

auto func1 = std::bind(userFun1, data, 3, 4);
auto func2 = std::bind(userFun2, data, 1, 2, "algorithm");
// ... and later,
func1();
func2();

Note the auto in those: you can't (portably) write the type of any object returned by std::bind, and those two example objects will have different types. But both of those would implicitly convert to the common type std::function<void()>. You can store a bunch of std::function<void()> objects in nearly any container type you want, to be used later:

std::map<unsigned int, std::function<void()>> func_map;
func_map[0] = std::bind(userFun1, data, 3, 4);
func_map[1] = std::bind(userFun2, data, 1, 2, "algorithm");
// ...
auto func_iter = func_map.find(key);
if (func_iter != func_map.end() && func_iter->second) {
    (func_iter->second)();
}

Of course, if you want to use your name of the function, and/or if you need the explicit common return type to work well with other templates or something, you can just wrap std::bind:

template <class F, class... Args>
std::function<void()> MapFun(F&& f, Args&&... args) {
    return std::bind<void>(std::forward<F>(f), std::forward<Args>(args)...);
}

Or if your MapFun is supposed to store the callable object inside some class member container or something, not just create and return one (it's not clear), it can of course do that by using std::bind and then adding the result to whatever container you need.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Thank you! I think the variadic std::bind one is what I am looking for. I guess `MapFun` does not know the signature of `f` right? Let me try some tests, those packed parameters are complicated to me... – J.Yang Nov 13 '18 at 02:11
0

If you only need to backport std::invoke from C++17, well, its simplest form is quite straightforward:

template<class F, class... Args> decltype(auto) invoke(F &&f, Args &&...args) {
    return ::std::forward<F>(f)(::std::forward<Args>(args)...);
}
bipll
  • 11,747
  • 1
  • 18
  • 32
  • Thanks for reply. Do you suggest me write `MapFun()` to store user function, and invoke it as you suggested? `template void MapFun(std::function f, ConstrT&&... args){ // store f }`. Would that idea work? – J.Yang Nov 13 '18 at 01:33
  • Not as you type it. By `Args...`, do you mean `ConstrT...`? Where are you planning to store it? Besides your question's title, there is no indication that you are planning to store `f` anywhere. – bipll Nov 13 '18 at 01:38
  • Sorry `Args...` is different to `ConstrT`. I wrote it wrong. `Args...` are inputs for `f`. `args` are other inputs for `MapFun`, and it probably includes `Args...`. `f` is defined by user, and its signature is not predictable. So if I use `std::invoke`, can I invoke a function with signature unknown? – J.Yang Nov 13 '18 at 01:54
  • Yes, as long as it can be invoked with that set of arguments. – bipll Nov 13 '18 at 07:21