Inferring the Call Signature of a Lambda or Arbitrary Callable for "Make_Function"

Inferring the call signature of a lambda or arbitrary callable for make_function

I've come up with a fairly nasty non-library solution, using the fact that lambdas have operator():

template<typename T> struct remove_class { };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...)> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); };

template<typename T>
struct get_signature_impl { using type = typename remove_class<
decltype(&std::remove_reference<T>::type::operator())>::type; };
template<typename R, typename... A>
struct get_signature_impl<R(A...)> { using type = R(A...); };
template<typename R, typename... A>
struct get_signature_impl<R(&)(A...)> { using type = R(A...); };
template<typename R, typename... A>
struct get_signature_impl<R(*)(A...)> { using type = R(A...); };
template<typename T> using get_signature = typename get_signature_impl<T>::type;

template<typename F> using make_function_type = std::function<get_signature<F>>;
template<typename F> make_function_type<F> make_function(F &&f) {
return make_function_type<F>(std::forward<F>(f)); }

Any ideas where this can be simplified or improved? Any obvious bugs?

What is the best type for a callable object in a template method?

In general, I do not like passing callable objects by const reference, because it is not that flexible (e.g. it cannot be used on mutable lambdas). I suggest to pass them by value. If you check the stl algorithms implementation, (e.g. for std::for_each), all of the callable objects are passed by value as well.

Doing this, the users are still able to use std::ref(func) or std::cref(func) to avoid unnecessary copying of the callable object (using reference_wrapper), if desired.

How to get a meaningful function signature from anything callable

Impossible. A function object can have overloaded or templated operator(). Thus the idea of it having "a signature" simply doesn't apply, because it can have an unbounded number of signatures.

If you restrict it to only having one signature, then you can take the address of operator() and then get the arguments from the member function pointer type using regular template specialization.

Removing member-ness of function signature type? (operator() of lambda)

Something along these lines:

#include <functional>

template <typename T>
struct MemberToFunction;

template <typename R, typename T, typename ... Args>
struct MemberToFunction<R (T::*)(Args...)> {
using type = R(Args...);
};

template <typename R, typename T, typename ... Args>
struct MemberToFunction<R (T::*)(Args...) const> {
using type = R(Args...);
};

int main()
{
auto l = [](bool b){ return b ? 1 : 0; };
std::function<MemberToFunction<decltype(&decltype(l)::operator())>::type> f(l);
}

Demo

How to comprehend that the type of `std::function` depends solely on its call signature?

The template type effectively denotes a function signature (excluding name of course).

std::function<bool(Bar const&, Foo const&)>

Can hold a functor, member function pointer, function pointer or lambda. But the callable must have the bool (Bar const&, Foo const&) signature.

class Foo {};
class Bar {};
class FunctorEx
{
public:
bool operator()(Bar const&, Foo const&)
{
return true;
}
} FunctorExInst;

class MemFunction
{
public:
bool MemFunctionEx(Bar const&, Foo const&)
{
return true;
}
} MemFunctionInst;

bool FunctionEx(Bar const&, Foo const&)
{
return true;
}

int main()
{
auto LambdaEx = [] (Bar const&, Foo const&) -> bool
{
return true;
};

std::function<bool(Bar const&, Foo const&)> exFunctionVar;
exFunctionVar = std::bind(&MemFunction::MemFunctionEx, &MemFunctionInst, std::placeholders::_1, std::placeholders::_2);
exFunctionVar = FunctorExInst;
exFunctionVar = FunctionEx;
exFunctionVar = LambdaEx;
}

Despite MemFunctionInst, FunctorExInst, FunctionEx, LambdaEx all being of different types, they can all be assigned to the same std::function variable due to a technique called type erasure.

Getting the function prototype of a lambda

Here is a small program that generates a function object for an arbitrary lambda:

#include <functional>
#include <type_traits>

template <typename R, typename... A>
class build_func_type
{
public:
using type = ::std::function<R(A...)>;
};

template <typename R, typename C, typename... A>
typename build_func_type<R, A...>::type mem_func_to_func( R(C::*)(A...) const)
{
return nullptr;
}

template <typename T>
decltype(mem_func_to_func(&T::operator ())) lambda_to_fp(T le)
{
using func_t = decltype(mem_func_to_func(&T::operator ()));
return func_t{le};
}

int test()
{
auto foo = [](int x) -> int { return x * x; };
auto ftype = lambda_to_fp(foo);
return ftype(5);
}

It uses the function mem_func_to_func to auto-deduce the type of the lambda's operator (). It then uses the build_func_type template to build a function type out of the components of the type of the lambda's operator (). I could possibly have used a constructor in build_func_type and relied on C++17 constructor type deduction for this.

Then lambda_to_fp will take a lambda, using mem_func_to_func to create a pointer to the appropriate function type from a pointer to the lambda's operator () member function. Then it creates a ::std::function of the appropriate type, constructing that type from the type of the function pointer. Then it initializes it with the lambda and returns it.

How to convert a lambda to an std::function using templates

You can't pass a lambda function object as an argument of type std::function<T> without explicitly specifying the template argument T. Template type deduction tries to match the type of your lambda function to the std::function<T> which it just can't do in this case - these types are not the same. Template type deduction doesn't consider conversions between types.

It is possible if you can give it some other way to deduce the type. You can do this by wrapping the function argument in an identity type so that it doesn't fail on trying to match the lambda to std::function (because dependent types are just ignored by type deduction) and giving some other arguments.

template <typename T>
struct identity
{
typedef T type;
};

template <typename... T>
void func(typename identity<std::function<void(T...)>>::type f, T... values) {
f(values...);
}

int main() {
func([](int x, int y, int z) { std::cout << (x*y*z) << std::endl; }, 3, 6, 8);
return 0;
}

This is obviously not useful in your situation though because you don't want to pass the values until later.

Since you don't want to specify the template parameters, nor do you want to pass other arguments from which the template parameters can be deduced, the compiler won't be able to deduce the type of your std::function argument.



Related Topics



Leave a reply



Submit