Can we get the type of a lambda argument?
Solution 1
It's not desirable in the general case. (Note that it's quite easy for std::function<T(A)>
to specify what e.g. argument_type
is: it's just A
! It's available in the type definition.)
It would be possible to require each and every function object type to specify its argument types, and in turn mandate that the closure types generated from lambda expression do so. In fact, pre-C++0x features like adaptable functors would only work for such types.
However, we're moving from that with C++0x and for good reasons. The simplest of which is simply overloading: a functor type with a templated operator()
(a.k.a a polymorphic functor) simply takes all kind of arguments; so what should argument_type
be? Another reason is that generic code (usually) attempts to specify the least constraints on the types and objects it operates on in order to more easily be (re)used.
In other words, generic code is not really interested that given Functor f
, typename Functor::argument
be int
. It's much more interesting to know that f(0)
is an acceptable expression. For this C++0x gives tools such as decltype
and std::declval
(conveniently packaging the two inside std::result_of
).
The way I see it you have two choices: require that all functors passed to your template use a C++03-style convention of specifying an argument_type
and the like; use the technique below; or redesign. I'd recommend the last option but it's your call since I don't know what your codebase looks like or what your requirements are.
For a monomorphic functor type (i.e. no overloading), it is possible to inspect the operator()
member. This works for the closure types of lambda expressions.
So we declare these helpers
template<typename F, typename Ret, typename A, typename... Rest>
A
helper(Ret (F::*)(A, Rest...));
template<typename F, typename Ret, typename A, typename... Rest>
A
helper(Ret (F::*)(A, Rest...) const);
// volatile or lvalue/rvalue *this not required for lambdas (phew)
that accept a pointer to member function taking at least one argument. And now:
template<typename F>
struct first_argument {
typedef decltype( helper(&F::operator()) ) type;
};
[ an elaborate trait could successively query the lvalue-rvalue/const/volatile overloads and expose the first argument if it's the same for all overloads, or use std::common_type
.]
Solution 2
@Luc's answer is great but I just came across a case where I also needed to deal with function pointers:
template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);
template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);
template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));
This can be used on both functors and function pointers:
void function(float);
struct functor {
void operator() (int);
};
int main() {
std::cout << std::is_same<first_argument<functor>, int>::value
<< ", "
<< std::is_same<first_argument<decltype(&function)>, int>::value
<< std::endl;
return 0;
}
![Chris Caulfield](https://i.stack.imgur.com/Yoior.jpg?s=256&g=1)
Chris Caulfield
Updated on June 12, 2022Comments
-
Chris Caulfield about 2 years
Using
std::function
, we can get the type of an argument using theargument_type
,second_argument_type
etc. typedefs, but I can't see a way to do the same thing with lambdas. Is it possible? (I'm using VS2010)Say I want something like the following in my deserialization system used to read an object and pass it to a setter function:
template<typename F> static void forward(F f) { // Create an object of the type of the first // parameter to the function object F typedef typename F::argument_type T; T t; //...do something with 't' here (deserialize in my case) // Forward the object to the function f(t); }
It can be used like this and everything works fine:
std::function<void(int)> f = [](int i) -> void { setValue(i); }; forward(f);
But it will not work directly with lambdas:
forward([](int i) -> void { setValue(i); }); //error C2039: 'argument_type' : is not a //member of '`anonymous-namespace'::<lambda1>'
Is there a way to access the parameter types in a way that will work for both lambdas and
std::function
objects? Maybe a way to get thestd::function
type of a lambda first, and then theargument_type
from that?
Following on from the answer below, a version that works with lambdas and
std::function
is:template<typename T, typename F> static void forward(F f) { T t; //...do something with 't' here (deserialize in my case) f(t); } forward<int>([](int i) -> void { setValue(i); });
Since
int
is repeated here I was hoping to get rid of it - not so bad forint
but more annoying for long-named types in a couple of namespaces. C'est la vie! -
Chris Caulfield almost 13 yearsGreat explanation thanks. I opted for the redesign (type 'T' is also a template parameter) but I was just hoping I would be able to get rid of it somehow since it seemed like unnecessary duplication.
-
Chris Caulfield almost 13 yearsMy real code actually does have captured variables (usually the object on which to call the setter method) but thanks for the clarification.
-
Dennis Zickefoose almost 13 yearsShouldn't you be able to do something similar to that, but with
&F::operator()
and pointer-to-member-functions instead of analyzingF
directly? That would work for lambdas that accept captures as well, I would think. -
Luc Danton almost 13 years@Dennis Hah, yes, this is allowed. I sure hope that my rewriting will not make me forget about the technique again.
-
ildjarn almost 13 years@Dennis : That would work for lambdas, but not for hand-written polymorphic functors or functors with overloaded
operator()
, which makes it non-viable in my opinion. -
Dennis Zickefoose almost 13 years@ildjarn: Ah, yeah, I can see how those would interfere with a general purpose solution
-
doug65536 about 8 yearsOriginally I reported a problem with this, but now I realize that I made a mistake. Here it is online.
-
Justin about 7 yearsNote that with C++17, you can use
std::function
's deduction guides. Internally, they either use function pointers or inspecting the type'soperator()
. This could easily be combined with a manual type trait to extract arguments and return type.