Можно ли выяснить тип параметра и тип возврата лямбда?
Учитывая лямбду, возможно ли выяснить ее тип параметра и тип возвращаемого значения? Если да, то как?
В основном я хочу lambda_traits
который можно использовать следующими способами:
auto lambda = [](int i) { return long(i*10); };
lambda_traits<decltype(lambda)>::param_type i; //i should be int
lambda_traits<decltype(lambda)>::return_type l; //l should be long
Мотивация заключается в том, что я хочу использовать lambda_traits
в шаблоне функции, который принимает лямбду в качестве аргумента, и мне нужно знать, что это тип параметра и тип возвращаемого значения внутри функции:
template<typename TLambda>
void f(TLambda lambda)
{
typedef typename lambda_traits<TLambda>::param_type P;
typedef typename lambda_traits<TLambda>::return_type R;
std::function<R(P)> fun = lambda; //I want to do this!
//...
}
Пока можно предположить, что лямбда принимает ровно один аргумент.
Изначально я пытался работать с std::function
как:
template<typename T>
A<T> f(std::function<bool(T)> fun)
{
return A<T>(fun);
}
f([](int){return true;}); //error
Но это, очевидно, даст ошибку. Я изменил это на TLambda
версия шаблона функции и хотите построить std::function
объект внутри функции (как показано выше).
5 ответов
Забавно, я только что написал function_traits
реализация, основанная на специализации шаблона на лямбде в C++0x, который может давать типы параметров. Хитрость, как описано в ответе на этот вопрос, заключается в использовании decltype
лямбды operator()
,
template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
enum { arity = sizeof...(Args) };
// arity is the number of arguments.
typedef ReturnType result_type;
template <size_t i>
struct arg
{
typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
// the i-th argument is equivalent to the i-th tuple element of a tuple
// composed of those arguments.
};
};
// test code below:
int main()
{
auto lambda = [](int i) { return long(i*10); };
typedef function_traits<decltype(lambda)> traits;
static_assert(std::is_same<long, traits::result_type>::value, "err");
static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");
return 0;
}
Обратите внимание, что это решение не работает для общего лямбда, как [](auto x) {}
,
Хотя я не уверен, что это строго соответствует стандартам, ideone скомпилировал следующий код:
template< class > struct mem_type;
template< class C, class T > struct mem_type< T C::* > {
typedef T type;
};
template< class T > struct lambda_func_type {
typedef typename mem_type< decltype( &T::operator() ) >::type type;
};
int main() {
auto l = [](int i) { return long(i); };
typedef lambda_func_type< decltype(l) >::type T;
static_assert( std::is_same< T, long( int )const >::value, "" );
}
Однако это обеспечивает только тип функции, поэтому из него должны быть извлечены типы результатов и параметров. Если вы можете использовать boost::function_traits
, result_type
а также arg1_type
встретит цель. Так как ideone, похоже, не обеспечивает повышение в режиме C++11, я не смог опубликовать реальный код, извините.
Метод специализации, показанный в ответе @KennyTMs, может быть расширен, чтобы охватить все случаи, включая вариабельные и изменчивые лямбды:
template <typename T>
struct closure_traits : closure_traits<decltype(&T::operator())> {};
#define REM_CTOR(...) __VA_ARGS__
#define SPEC(cv, var, is_var) \
template <typename C, typename R, typename... Args> \
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv> \
{ \
using arity = std::integral_constant<std::size_t, sizeof...(Args) >; \
using is_variadic = std::integral_constant<bool, is_var>; \
using is_const = std::is_const<int cv>; \
\
using result_type = R; \
\
template <std::size_t i> \
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
};
SPEC(const, (,...), 1)
SPEC(const, (), 0)
SPEC(, (,...), 1)
SPEC(, (), 0)
Обратите внимание, что арность не корректируется для вариационной operator()
s. Вместо этого можно также рассмотреть is_variadic
,
Ответ, предоставленный @KennyTMs, прекрасно работает, однако, если лямбда не имеет параметров, использование индекса arg<0> не компилируется. Если у кого-то еще была эта проблема, у меня есть простое решение (проще, чем использование решений, связанных с SFINAE, то есть).
Просто добавьте void в конец кортежа в структуре arg после различных типов аргументов. т.е.
template <size_t i>
struct arg
{
typedef typename std::tuple_element<i, std::tuple<Args...,void>>::type type;
};
поскольку арность не зависит от фактического количества параметров шаблона, фактические значения не будут неправильными, и если оно равно 0, то по крайней мере аргумент<0> все равно будет существовать, и вы можете делать с ним все, что захотите. Если вы уже планируете не превышать индекс arg<arity-1>
тогда это не должно мешать вашей текущей реализации.
Если вы ищете полное решение для всех типов в C++, которые можно вызывать, многие из этих ответов работают, но упускают некоторые краеугольные случаи, например
- Ссылка на лямбда
- Функции и указатели функций
Вот полное решение, насколько мне известно (за исключением общих лямбда-выражений) — дайте мне знать в комментариях, если чего-то не хватает:
template <typename>
struct closure_traits;
template <typename FunctionT> // overloaded operator () (e.g. std::function)
struct closure_traits
: closure_traits<decltype(&std::remove_reference_t<FunctionT>::operator())>
{
};
template <typename ReturnTypeT, typename... Args> // Free functions
struct closure_traits<ReturnTypeT(Args...)>
{
using arguments = std::tuple<Args...>;
static constexpr std::size_t arity = std::tuple_size<arguments>::value;
template <std::size_t N>
using argument_type = typename std::tuple_element<N, arguments>::type;
using return_type = ReturnTypeT;
};
template <typename ReturnTypeT, typename... Args> // Function pointers
struct closure_traits<ReturnTypeT (*)(Args...)>
: closure_traits<ReturnTypeT(Args...)>
{
};
// member functions
template <typename ReturnTypeT, typename ClassTypeT, typename... Args>
struct closure_traits<ReturnTypeT (ClassTypeT::*)(Args...)>
: closure_traits<ReturnTypeT(Args...)>
{
using class_type = ClassTypeT;
};
// const member functions (and lambda's operator() gets redirected here)
template <typename ReturnTypeT, typename ClassTypeT, typename... Args>
struct closure_traits<ReturnTypeT (ClassTypeT::*)(Args...) const>
: closure_traits<ReturnTypeT (ClassTypeT::*)(Args...)>
{
};
Отказ от ответственности:std::remove_reference
был вдохновлен этим кодом.