Reputation: 606
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
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;
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
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
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
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
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