How to determine function signature of a callable object?
Is there a way in C++ to determine function signature of a callable object?
Consider following:
template< typename F >
void fun(F f)
{
// ...
}
Lets assume that fun
is called only with callable "things".
Inside of fun
I want to know what is the signature of function f
. That should work with function pointers, refer开发者_如何学编程ences, wrappers, lambdas, binds, function objects (providing they have only one operator ()
) and so on. I'm limited with Visual Studio 2010 SP 1 but am interested in standard solutions even if not working on that compiler.
(A function signature is Return_Type ([Arg1_Type [, Arg2_Type [, ... ] ] ])
; same as given to std::function
/boost::function
.)
A partial solution of knowing at least the return value of f
is of some value to. (I have tried std::result_of
but couldn't get it to work in any case I tried.)
On C++0x compliant compilers, you can at least get the result type of f()
by using decltype(f())
. Visual C++ 2010 should support decltype, though I haven't checked it myself yet. As for getting the argument types, I'm not sure if there's a way that would work with function pointers.
Edit
Boost.Function seems to have it figured out, at least on some compilers (it doesn't work on old versions of VC++ or Borland C++ for instance). It can wrap function pointers and extract arguments for them. The solution seems quite complex however, and it involves defining multiple templates with Boost.PP. If you feel like trying to re-implement everything you can certainly try that, but I think you can also just use a dummy Boost.Function wrapper to make things easier, e.g. boost::function<decltype(f)>::second_argument_type
to get the second argument type.
You may look at Boost Function Types:
http://www.boost.org/doc/libs/1_46_1/libs/function_types/doc/html/boost_functiontypes/introduction.html
While trying to solve this I came up with following partial solution:
#include <cstdlib>
#include <functional>
#include <iostream>
#include <typeinfo>
#include <boost/bind.hpp>
#include <boost/function.hpp>
template< typename T >
struct identity
{
typedef T type;
};
// ----------
// Function signature metafunction implementation
// Also handler for function object case
// ----------
template< typename T >
struct function_signature_impl
: function_signature_impl< decltype( &T::operator() ) >
{
};
// ----------
// Function signature specializations
// ----------
template< typename R >
struct function_signature_impl< R () >
: identity< R () >
{
};
template< typename R, typename A1 >
struct function_signature_impl< R ( A1 ) >
: identity< R ( A1 ) >
{
};
template< typename R, typename A1, typename A2 >
struct function_signature_impl< R ( A1, A2 ) >
: identity< R ( A1, A2 ) >
{
};
// ----------
// Function pointer specializations
// ----------
template< typename R >
struct function_signature_impl< R ( * )() >
: function_signature_impl< R () >
{
};
template< typename R, typename A1 >
struct function_signature_impl< R ( * )( A1 ) >
: function_signature_impl< R ( A1 ) >
{
};
// ----------
// Member function pointer specializations
// ----------
template< typename C, typename R >
struct function_signature_impl< R ( C::* )() >
: function_signature_impl< R () >
{
};
template< typename C, typename R, typename A1 >
struct function_signature_impl< R ( C::* )( A1 ) >
: function_signature_impl< R ( A1 ) >
{
};
template< typename C, typename R >
struct function_signature_impl< R ( C::* )() const >
: function_signature_impl< R () >
{
};
template< typename C, typename R, typename A1 >
struct function_signature_impl< R ( C::* )( A1 ) const >
: function_signature_impl< R ( A1 ) >
{
};
// ----------
// Function signature metafunction
// ----------
template< typename T >
struct function_signature
: function_signature_impl< T >
{
};
// ----------
// Tests
// ----------
template< typename F >
void test( F f )
{
typedef function_signature< F >::type signature_type;
std::cout << typeid( F ).name() << std::endl;
std::cout << '\t' << typeid( signature_type ).name() << std::endl;
std::cout << std::endl;
}
int foo( int )
{
return 0;
}
struct bar
{
int operator ()( int )
{
return 0;
}
};
struct cbar
{
int operator ()( int ) const
{
return 0;
}
};
struct abar1
{
int operator ()( int ) const
{
return 0;
}
int operator ()( int )
{
return 0;
}
};
struct abar2
{
int operator ()( int )
{
return 0;
}
int operator ()( double )
{
return 0;
}
};
struct mem
{
int f( int ) const
{
return 0;
}
};
int main()
{
test(
[]( int ) -> int { return 0; }
);
test(
foo
);
test(
&foo
);
test(
bar()
);
test(
cbar()
);
test(
std::function< int ( int ) >( &foo )
);
test(
boost::function< void ( int ) >( &foo )
);
/*
test(
std::bind( &mem::f, mem(), std::placeholders::_1 )
);
*/
/*
test(
boost::bind( &mem::f, mem(), _1 )
);
*/
/*
test(
abar1()
);
*/
/*
test(
abar2()
);
*/
return EXIT_SUCCESS;
}
(No code for checking agains inproper arguments was added.)
The idea is that function_signature< decltype( f ) >::type
should be the signature of a call of f( ... )
where that "..." is the signature. This means in particular that pointer to member function is an invalid argument here (although the code does not check against this) since such pointer cannot be "called" directly.
At the end are tests which fail (in VS 2010). All due to operator ()
being overloaded. And this makes that code mostly useless as it will not work with the result of bind
. But maybe it can be further developed.
Answer to André Bergner's query:
function_signature_impl
never derives from itself. It is a type template which only means a loosely coupled family of actual types. But the actual types (even thou they belong to the same family) are distinct types.
The &T::operator()
is a pointer to a call operator (operator()
) of type T
– obviously. Basically just a member function pointer (where the member function happens to be a call operator). While decltype
of it is the type of that pointer. This might seem insignificant (especially that type_info::name
of both shows the same) but for templates it does matter since one is a pointer while the other is a type (apparently).
This “case” is needed to cover for functors (types which objects are “callable”). Note that this unspecialized function_signature_impl
is used only if the template argument T
doesn’t match anything else among the listed “cases”.
I hope I got it right after that long time. Although I’m not sure if I ever truly and fully understood it. The code was a bit result of experimenting.
This answer was just given to me by SlashLife on freenode ##c++:
template <typename T, typename Signature>
struct signature_impl;
template <typename T, typename ReturnType, typename... Args>
struct signature_impl<T, ReturnType(T::*)(Args...)>
{
using type = ReturnType(Args...);
};
template <typename T>
using signature_t = signature_impl<T, decltype(&T::operator())>;
The caveats are that it only works if there is a unique operator()
and it doesn't work for lambdas.
You can use std::is_invocable_r
精彩评论