Using variadic template arguments to resolve a lambda signature
While there's a lot of stuff floating out there about getting the return type of any templated callback function/method (including lambdas of course), I'm having an extremely hard time finding information about resolving the full call signature of a lambda function. At least in gcc 4.7 it seems to be an edge case where the normal tricks (see below) don't work. Here's what I'm trying to do and have so far (a stripped down version of course)...
template<typename Sig>
struct invokable_type { };
template<typename R, typename...As>
struct invokable_type<R(As...)> {
static constexpr size_t n = sizeof...(As);
typedef R(callable_type)(As...);
template<size_t i>
struct arg {
typedef typename peel_type<i, As...> type;
};
};
peel_type<size_t, typename...>
is not included here for brevity but it's a simple argument type peeler (I think there's one built in to C++11, but I never bothered to look). It's unimportant for this question.
Then, of course, specializations (and further properties/typedefs) exist for a myriad of callable types such as R(*)(As...)
, R(&)(As...)
, (R(T::*)(As...)
, std::function<R(As...)>
, method cv qualifiers, method lvalue/rvalue qualifiers, etc, etc, etc.
Then, somewhere down the road we have a lovely function or method (function here, doesn't matter) that looks like...
template<typename C, typename...As>
static void do_something(C&& callback, As&&...as) {
do_something_handler<invokable_type<C>::n, As...>::something(std::forward<C>(callback), std::forward<As>(as)...);
}
Never mind what do_something_handler
does... it's entirely immaterial. The problem lies with lambda functions.
For all possible generic invokable signatures I've specialized for (which appears to be all but non-STL functors), this works beautifully when do_something()
is called with them as the first argument (template deduction fully works). However, lambda functions result in an uncaptured type signature, resulting in invokable_type<Sig>
being used, which means things like ::n
and ::args<0>::type
simply don't exist.
Not-a-problem example...
void something(int x, int y) {
return x * y;
}
... and later...
do_something(something, 7, 23);
Problem example...
do_something([](int x, int y) {
return x * y;
}, 7, 23);
If I understand lambda functions correctly, the compiler is likely to compile this lambda to a static function within the "namespace" of the defining scope (gcc certainly seems to). For the life of me I can't figure out what the signature actually is though. It looks like it definitely has one that should be deducible via template specialization (based on error reporting).
Another tangential question is even if there is a signature I can use, how cross-compiler dangerous this this? Are lambda compilation signatures standardized or is it all across the board?
Summing up and extending from the comments:
Per [expr.prim.lambda]/3, the type of a lambda-expression is a class type, just like "ordinary, named function object types":
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type [...]
Further down, /5 specifies:
The closure type for a lambda-expression has a public inline
function call operator (13.5.4) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively. This function call operator is declared const
(9.3.1) if and only if the lambda-expression's parameter-declaration-clause is not followed by mutable. It is neither virtual nor declared volatile
. [...]
(it then continues by specifying attributes and exception-specifications)
Which means that the lambda [](int p){ return p/2.0; }
[](int p){ return p/2.0; }
behaves in this regard exactly like
struct named_function_object
{
double operator() (int p) const { return p/2.0; }
};
Therefore, your first specialization
template<typename R, typename...As>
struct invokable_type<R(As...)>;
should already be able to deal with lambdas. The SSCCE
#include <utility>
template<class T>
struct decompose;
template<class Ret, class T, class... Args>
struct decompose<Ret(T::*)(Args...) const>
{
constexpr static int n = sizeof...(Args);
};
template<class T>
int deduce(T t)
{
return decompose<decltype(&T::operator())>::n;
}
struct test
{
void operator() (int) const {}
};
#include <iostream>
int main()
{
std::cout << deduce(test{}) << std::endl;
std::cout << deduce([](int){}) << std::endl;
}
compiles fine on recent versions of clang++ and g++. It seems the problem is related to g++4.7
Further research shows that g++-4.7.3 compiles the above example.
The problem might be related to the misconception that a lambda-expression would yield a function type. If we define do_something
as
template<class C>
void do_something(C&&)
{
std::cout << invokable_type<C>::n << std::endl;
}
Then for a call like do_something( [](int){} )
, the template parameter C
will be deduced to the closure type (no reference), ie a class type. The analogous case for the struct test
defined above, would be do_something( test{} )
, in which case C
would be deduced to test
.
The specialization of invokable_type
that is instantiated is therefore the general case
template<class T>
struct invokable_type;
as T
in both cases is not a "composite type" like a pointer or function type. This general case can be used by assuming it only takes a pure class type, and then using the member T::operator()
of that class type:
template<class T>
struct invokable_type
{
constexpr static int n = invokable_type<&T::operator()>::n;
};
or, as Potatoswatter put it, via inheritance
template<class T>
struct invokable_type
: invokable_type<&T::operator()>
{};
Potatoswatter's version however is more general and probably better, relying on a SFINAE check for the existance of T::operator()
, which can provide a better diagnostic message if the operator cannot be found.
NB If you prefix a lambda-expression that doesn't capture anything with a unary +
, it'll be converted to a pointer-to-function. do_something( +[](int){} )
will work with a specialization invokable_type<Return(*)(Args...)>
.
As DyP mentions, lambda functors are guaranteed to have a public operator ()
. Note that operator ()
cannot be static
or non-member.
(A type can also be callable due to the existence of a conversion operator to function pointer type, and stateless lambdas do have such conversion operators, but they still must provide operator ()
.
You can get the signature of operator ()
using decltype( & T::operator() )
, provided there is only one overload, which is guaranteed for lambdas. This results in a pointer to member function type. You can use a metafunction to strip the T::
part off, or write metafunction queries against the PTMF directly.
#include <iostream>
#include <typeinfo>
#include <type_traits>
#include <tuple>
template< typename t, std::size_t n, typename = void >
struct function_argument_type;
template< typename r, typename ... a, std::size_t n >
struct function_argument_type< r (*)( a ... ), n >
{ typedef typename std::tuple_element< n, std::tuple< a ... > >::type type; };
template< typename r, typename c, typename ... a, std::size_t n >
struct function_argument_type< r (c::*)( a ... ), n >
: function_argument_type< r (*)( a ... ), n > {};
template< typename r, typename c, typename ... a, std::size_t n >
struct function_argument_type< r (c::*)( a ... ) const, n >
: function_argument_type< r (c::*)( a ... ), n > {};
template< typename ftor, std::size_t n >
struct function_argument_type< ftor, n,
typename std::conditional< false, decltype( & ftor::operator () ), void >::type >
: function_argument_type< decltype( & ftor::operator () ), n > {};
int main() {
auto x = []( int, long, bool ){};
std::cout << typeid( function_argument_type< decltype(x), 0 >::type ).name() << 'n';
std::cout << typeid( function_argument_type< decltype(x), 1 >::type ).name() << 'n';
std::cout << typeid( function_argument_type< decltype(x), 2 >::type ).name() << 'n';
}
http://coliru.stacked-crooked.com/a/57cd7bb76267ffda
链接地址: http://www.djcxy.com/p/51576.html