1

Consider the following example:

using function_pair = std::pair<int, void(*)(void*)>; // This line cannot change
template <class Arg, class F>
function_pair make_function_pair(int i, F&& f)
{
    return function_pair(i, 
    [&](void* x){std::forward<F>(f)(static_cast<Arg*>(x));}); // Not working
}

// Later in code
auto p1 = make_function_pair<char>(1, [](char* x){std::cout<<*x<<std::endl;});
auto p2 = make_function_pair<double>(2, [](double* x){*x = *x*2;});
auto p3 = make_function_pair<float>(3, [](float* x){*x = *x*3;});

The code fails to compile because of the capturing lambda (when the lambda does not have to capture f, it works). I am wondering how to make this work without using std::function, because the computing overhead of std::function is huge and I cannot afford that. And even without this practical reason, I am wondering how to solve this "academic" problem without using std::function.

Vincent
  • 57,703
  • 61
  • 205
  • 388
  • 2
    Do you need the second element of function pair to be a function pointer? Only non-capturing lambdas can be converted to one. OTOH, if the function pointer was just a starting point to illustrate what you are trying to do, then you are facing a brickwall with C++11. With C++14, simply use `auto` for the function return type and you avoid the overhead of `std::function` – Pradhan Dec 01 '15 at 18:22
  • I second Pradhan -- as the type of the returned lambda cannot be expressed, your only choice in C++11 is to erase it behind an std::function or similar. – Quentin Dec 01 '15 at 18:24
  • @Pradhan Yes, I need a function pointer, so that p1, p2 and p3 will all be of the same type. – Vincent Dec 01 '15 at 18:24
  • Did you measure the overhead of `std::function`? I doubt it, because you are also capturing by reference, and capturing by reference is a big no-no when you are returning a copy of the lambda from the current scope. The overhead of a `std::function` is larger than a function pointer, but it isn't ridiculously more if you fall within the small function optimization size. – Yakk - Adam Nevraumont Dec 01 '15 at 18:29
  • After @Yakk's comment, I re-read the code and no longer understand the intent. What is the lambda forwarding in `make_function_pair` trying to achieve? It is wrapping around `f` and capturing nothing else. Why not simply use `f`, which is convertible to a function pointer? For example, `auto p1 = std::make_pair(1, +[](char* x){std::cout<<*x< – Pradhan Dec 01 '15 at 18:36
  • 2
    @Pradhan converting from `void(char*)` to `void(void*)` is undefined behavior. – Yakk - Adam Nevraumont Dec 01 '15 at 18:39
  • [In C++20, (stateless) lambda functions are default constructible](https://stackoverflow.com/questions/38722631/why-is-a-lambda-in-c-never-defaultconstructible#comment113972813_38723264), so the problem can be solved. – user202729 Aug 18 '21 at 08:36

2 Answers2

2

The hack solution is to rely on the fact that a non-capturing lambda doesn't use its state in every C++ implementation I've seen.

template<class F>
struct stateless_t {
  constexpr stateless_t() {
    static_assert( std::is_empty<F>::value, "Only works with stateless lambdas" );
  }
  using F_ref = F const&;
  template<class...Ts>
  std::result_of_t<F_ref(Ts...)> operator()(Ts&&...ts)const {
    return (*reinterpret_cast<F const*>(this))(std::forward<Ts>(ts)...);
  }
};

template<class F>
stateless_t<F> stateless() { return {}; }

template <class Arg, class F>
function_pair make_function_pair(int i, F const&)
{
  return function_pair(
    i, 
    [](void* x){return stateless<F>()(static_cast<Arg*>(x));}
  );
}

This is undefined behavior left right and center, but will probably work with your compiler.

Better alternatives is to use std::function and measure the overhead before rejecting the option. With small function objects, std::function has only modest overhead (virtual call instead of function pointer call).

Next, write your own std::function with reduced overhead or find one (like fastest possible delegates). You can, for example, make a faster std::function that stores the pointer-to-invoke within the class itself rather then in a vtable.

Or, also UB, but better than above -- convert your stateless lambda to void(T*), then reinterpret_cast that to void(void*). While still UB, most implementations use binary-compatible pointer sizes, and function pointers that can be cast between. @Pradhan suggested this above in comments.

In general, avoid UB, as it adds a constant maintenance overhead from that point onward. You have tested it in your current compiler, but you have to check it works in every compiler and every build with every compiler setting used from now until the end of life of the code for it to be reliable long-term.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

There is no way to have a capturing lambda convertable to a pointer to function. If you think about it, it is rather clear - to capture the variables you need them to be passed into function somehow - and function pointer does not have that option.

Academic solution is to re-implement std::function. No way around this.

SergeyA
  • 61,605
  • 5
  • 78
  • 137