How do I extract and execute a target from a std::function

711 Views Asked by At

I have something equal to this:

auto dummy(std::function<int(int)> ifunc) {
  auto vals = std::vector<int>(1000000);
  for(auto& v: vals) {
    v = ifunc(v);
  }
}

Assuming the std::function does not bind to a lambda with state (althrough it might be bound to a lambda without state), what is the correct way of extracting the underlaying function pointer and exectuing it instead of the std::function?

This is my attempt, but it crashes regardles if I dereference the target or not.

auto dummy(std::function<int(int)> ifunc) {
  auto vals = std::vector<int>(1000000);
  auto&& target = *ifunc.target<int(int)>();
  for(auto& v: vals) {
    v = target(v);
  }
}
3

There are 3 best solutions below

1
On

The reason that std::function exists is to provide an abstraction of a function that has a particular signature. The actual function (or function object) held in the std::function object does not have to have the signature that the std::function object provides. For example, your function<int(int)> object could hold a pointer to a function that takes double and returns char; the code inside the std::function object takes care of the conversions.

The target template member function returns a pointer to the target object only if the target object has the exact type specified by the template argument to target. So, for example, if the target object takes a double and returns char, the calling ifunc.target<char(double)> will give you back a pointer to that function. Calling it with any other argument type will give you back a null pointer.

In the code presented here, the call is ifunc.target<int(int)>(), and that will return a null pointer unless the exact type of the target object is "function taking int and returning int".

If you're going to use this template member function, you must check for a null pointer before doing anything else with the returned value.

0
On

This is a way to make it work:

#include <iostream>
#include <functional>
#include <vector>
#include <cassert>

auto dummy(std::function<int(int)> ifunc) {
  auto vals = std::vector<int>(10);
  auto&& target = *ifunc.target<int(*)(int)>(); //added the missing (*)
  assert(target);
  for(auto& v: vals) {
    v = target(v);
  }
}

int main() {
    dummy(+[](int x) {return x+1;}); //explicitly decay to function pointer
    return 0;
}

Note the added (*) to get the syntax right (std::function is storing a function pointer, not a function) and that we have to explicitly decay the lambda into a function pointer.

Since you can't really know the type of the lambdas you would have to go through the code and add a + where possible or wrap the lambda into a decay_if_possible-function before passing it to std::function.

0
On

It seems you want to get the target for making the call to the function faster. Let me propose a solution that don't require your functoin to be callable with only one type possible.

By the way, the fact that you need to send the exact type in target makes your function effectively callable with that type only. At that point, you shouldn't hide it and take that type as parameter to the function. Let me explain:

// If ifunc don't contain a int(*)(int), runtime error!
auto&& target = ifunc.target<int(*)(int)>();

Your code will compile if you send any other types, such as lambdas, but will make a runtime error.

By taking the parameter by that type, it indeed makes your code callable with that type only, but when trying to call it with another type, it result in a compile time error:

auto dummy(int(*ifunc)(int)) { // safer!
  auto vals = std::vector<int>(1000000);
  for(auto& v: vals) {
    v = ifunc(v);
  }
}

But what if you need speed and you need to call your function with any closure type? There's a language feature that exist just for that: templates!

template<typename F>
auto dummy(F function) { // safer, faster than function pointer, and scalable
  auto vals = std::vector<int>(1000000);

  for(auto& v: vals) {
    v = function(v);
  }
}

This version is even faster than pointer to function. Since the code is templated, the function you are sending there has a much greater chance to be inlined. You can see the resulting assembly for yourself.

By the way,

(althrough it might be bound to a lambda without state)

Not quite. You must explicitly convert the lambda to a function pointer first, or the internal closure type will still be the type of the lambda.