I've written a traits class that lets me extract information about the arguments and type of a function or function object in C++0x (tested with gcc 4.5.0). The general case handles function objects:
template <typename F>
struct function_traits {
template <typename R, typename... A>
struct _internal { };
template <typename R, typename... A>
struct _internal<R (F::*)(A...)> {
// ...
};
typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>;
};
Then I have a specialization for plain functions at global scope:
template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
// ...
};
This works fine, I can pass a function into the template or a function object and it works properly:
template <typename F>
void foo(F f) {
typename function_traits<F>::whatever ...;
}
int f(int x) { ... }
foo(f);
What if, instead of passing a function or function object into foo
, I want to pass a lambda expression?
foo([](int x) { ... });
The problem here is that neither specialization of function_traits<>
applies. The C++0x draft says that the type of the expression is a "unique, unnamed, non-union class type". Demangling the result of calling typeid(...).name()
on the expression gives me what appears to be gcc's internal naming convention for the lambda, main::{lambda(int)#1}
, not something that syntactically represents a C++ typename.
In short, is there anything I can put into the template here:
template <typename R, typename... A>
struct function_traits<????> { ... }
that will allow this traits class to accept a lambda expression?
The
void_t
trick can help. How does `void_t` work?Unless you have C++17, you'll need to include the definition of
void_t
:Add an extra template argument to the original template, defaulted to
void
:The traits object for simple functions is the same as you already have:
For non-const methods:
Don't forget
const
methods:Finally, the important trait. Given a class type, including lambda types, we want to forward from
T
todecltype(&T::operator())
. We want to ensure that this trait is only available for typesT
for which::operator()
is available, and this is whatvoid_t
does for us. To enforce this constraint, we need to put&T::operator()
into the trait signature somewhere, hencetemplate <typename T> struct function_traits<T, void_t< decltype(&T::operator())
The operator() method in (non-
mutable
, non-generic) lambdas isconst
, which explains why we need theconst
template above.But ultimately this is very restrictive. This won't work with generic lambdas, or objects with templated
operator()
. If you reconsider your design, you find find a different approach that is more flexible.I think it is possible to specialize traits for lambdas and do pattern matching on the signature of the unnamed functor. Here is the code that works on g++ 4.5. Although it works, the pattern matching on lambda appears to be working contrary to the intuition. I've comments inline.
By delegating some of the work to a series of function templates instead of a class template, you can extract the relevant info.
First though, I should say that the relevant method is a
const
method, for a lambda (for a non-capturing, non-generic, non-mutable
lambda). So you will not be able to tell the difference between a true lambda and this:Therefore, I must assume that you don't want "special treatment" for lambdas, and you don't want to test if a type is a lambda type, and that instead you want to simply extract the return type, and the type of all arguments, for any object which is simple enough. By "simple enough" I mean, for example, that the
operator()
method is not itself a template. And, for bonus information, a boolean to tell us if anoperator()
method was present and used, as opposed to a plain old function.