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.

7
  • 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 Commented Dec 1, 2015 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. Commented Dec 1, 2015 at 18:24
  • @Pradhan Yes, I need a function pointer, so that p1, p2 and p3 will all be of the same type. Commented Dec 1, 2015 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. Commented Dec 1, 2015 at 18:29
  • 2
    @Pradhan converting from void(char*) to void(void*) is undefined behavior. Commented Dec 1, 2015 at 18:39

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.