Vincent
Vincent

Reputation: 60411

Casting capturing lambda to function pointer not using std::function

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.

Upvotes: 1

Views: 879

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275600

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.

Upvotes: 2

SergeyA
SergeyA

Reputation: 62583

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.

Upvotes: 0

Related Questions