Harvey Ellis
Harvey Ellis

Reputation: 606

Passing lambdas as template parameters: what type is actually deduced?

If I pass a lambda as a template parameter, what is the actual type of that parameter that is deduced? I have looked on the VS2017 debugger and the type of this lambda: [](int x) {return x; } is filename::__I2::int<lambda>(int).

The reason I am asking this is because I want to pass a lambda and then create an internal std::function from this. Note that this relates to this answer and why we have to use CTAD to construct an internal std::function instead of just passing the template parameter to a std::function.

As an example, I would like to do something like the following:

template<class Func, class... Args> 
void createStdFunc(Func f, Args... args) {
    std::function<Func> internalFunc = f; //this does not work
}

//usage
createStdFunc([](int x) {return x; }, 5);

However, this does not work, and I get the error 'initialising' cannot convert from 'Func' to 'std::function<Func>'. I am not sure how the types differ and how they have changed from passing into the function to initialising the std::function. Please note that I do know you can use CTAD from 2017 onwards, but was wondering what a solution for 2014 and before would have been?

Upvotes: 1

Views: 545

Answers (5)

Michael Kenzel
Michael Kenzel

Reputation: 15951

The std::function template expects as its argument a function type from which it infers the return and parameter type for the callable to wrap. The closure type of a lambda expression is a callable, but it's not a function type.

C++17 introduced deduction guides for std::function which allow the correct type to be deduced from any callable argument. Pre C++17, you could use a set of helper templates to deduce the correct type, for example:

template <typename F>
struct deduce_func_type_helper;

template <typename R, typename... Args>
struct deduce_func_type_helper<R(&)(Args...)>
{
    using type = std::function<R(Args...)>;
};

template <typename R, typename... Args>
struct deduce_func_type_helper<R(*)(Args...)> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename C, typename R, typename... Args>
struct deduce_func_type_helper<R(C::*)(Args...)> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename C, typename R, typename... Args>
struct deduce_func_type_helper<R(C::*)(Args...) const> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename C, typename R, typename... Args>
struct deduce_func_type_helper<R(C::*)(Args...) volatile> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename F>
struct deduce_func_type_helper<F&> : deduce_func_type_helper<std::remove_cv_t<F>> {};

template <typename F>
struct deduce_func_type_helper<F&&> : deduce_func_type_helper<std::remove_cv_t<F>> {};

template <typename F>
struct deduce_func_type_helper : deduce_func_type_helper<decltype(&F::operator())> {};

template <typename F>
using func_type_t = typename deduce_func_type_helper<F>::type;

live example here

Note that above example is not complete; it's missing some specializations, e.g., for all possible combinations of const, volatile, and different ref qualifiers. So this can get quite verbose, you will probably want to go with C++17 if you can…

Upvotes: 1

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29022

You can write a simple trait to generalize callable types. If you handle both function pointers and anything with operator() (both const and non-const) you should be able to cover most use cases.

#include <tuple>

// For callable types
template<class T>
struct func_type : func_type<decltype(&T::operator())>{};

// For callable types' member functions (including `operator()`)
template<class T, class R, class ... Args >
struct func_type<R (T::*)(Args...) const> : func_type<R(*)(Args...)> {};

// For function pointers
template<class R, class ... Args >
struct func_type<R (*)(Args...)> {
    using type = R(Args...);
    using result = R;
    using args = std::tuple<Args...>;
};

template<class T>
using func_type_t = typename func_type<T>::type;

func_type_t<T> should then give you a function type for most callable types T. Example uses :

#include <functional>

template<class Func, class... Args>
void createStdFunc(Func f, Args... args) {
    // Replaced `Func` with `func_type_t<Func>`
    std::function<func_type_t<Func>> internalFunc = f;
}

int foo(int x) { return x; }

struct bar {
    int operator()(int x) { return x; };
};


int main()
{
    // With lambda expression
    createStdFunc([](int x) {return x; }, 5);

    // With function pointer
    createStdFunc(foo, 5);

    // With std::function
    std::function<int(int)> std_func = [](int x) {return x; };
    createStdFunc(std_func, 5);

    // With a functor
    createStdFunc(bar{}, 5);
}

Upvotes: 1

max66
max66

Reputation: 66240

My way

#include <iostream>
#include <functional>

template <typename R, typename T, typename ... As>
constexpr std::function<R(As...)> getFuncType (R(T::*)(As...) const);

template <typename F, typename ... As>
void createStdFunc (F const & f, As ... as)
 {
   decltype(getFuncType(&F::operator()))  internalFunc { f };

   internalFunc(as...);
 }

int main ()
 {
   createStdFunc([](int x) { std::cout << x << std::endl; }, 5);
 }

Maybe also through a using

template <typename F>
using funcType = decltype(getFuncType(&F::operator()));

template <typename F, typename ... As>
void createStdFunc (F const & f, As ... as)
 {
   funcType<F> internalFunc { f };

   internalFunc(as...);
 }

Upvotes: 2

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

The problem inside your code is that Func is not a function type. It's the type of the lambda. Lambdas compile down to something like this:

// equivalent:
// auto my_lambda = [](int v){ return v; };

struct /* unnamed */ {
    auto operator()(int v) const { return v; }
} my_lambda;

The solution would be to extract the type of the operator() from the closure type:

using my_lambda_t = decltype(my_lambda);

// type: int(my_lambda_t::*)(int) const; 
auto call_operator = &decltype(my_lambda_t)::operator();

Then, from the type of the operator(), you can deduce the type of the arguments and the return type:

template<typename>
struct extract_types {};

template<typename R, typename C, typename... Args>
struct extract_types<R(C::*)(Args...) const> {
    using result = R;
    using args_types = std::tuple<Args...>;
};

Generalized versions of this pattern is shipped in Boost.CallableTraits

Upvotes: 1

user7860670
user7860670

Reputation: 37598

In C++14 you can use return type deduction to figure out function signature, this implies that types of arguments passed into createStdFunc match:

template<class Func, class... Args> 
void createStdFunc(Func f, Args... args) {
    std::function<std::result_of_t<Func(Args...)> (Args...)> internalFunc{f}; //this does work
}

Upvotes: 3

Related Questions