How to Figure Out the Parameter Type and Return Type of a Lambda

Is it possible to figure out the parameter type and return type of a lambda?

Funny, I've just written a function_traits implementation based on Specializing a template on a lambda in C++0x which can give the parameter types. The trick, as described in the answer in that question, is to use the decltype of the lambda's 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;
}

Note that this solution does not work for generic lambda like [](auto x) {}.

deduce return type of lambda with arguments

If you are willing to limit yourself to non-generic lambdas (and in general to class objects with exactly one operator() overload), then something like this should work (not tested):

template <typename T, typename R, typename... Args>
R ResultOf(R (T::*)(Args...));

Used as

using R = decltype(ResultOf(&decltype(my_lambda)::operator()));

This could be wrapped in helper classes for nicer syntax; we leave this as an exercise for the reader.

How does compiler deduce return type from this lambda expression?

websocket::stream_base::decorator is a template function with one template type parameter.

template<class Decorator>
decorator( Decorator&& f );

In this call

ws_.set_option(websocket::stream_base::decorator(
[](websocket::response_type& res)
{
res.set(http::field::server,
std::string(BOOST_BEAST_VERSION_STRING) +
" websocket-server-async");
}));

the function is called accepting the lambda expression

[](websocket::response_type& res)
{
res.set(http::field::server,
std::string(BOOST_BEAST_VERSION_STRING) +
" websocket-server-async");
}

as its template argument. The lambda expression has by default the return type void because there is no return statement in the lambda expression.

How does the lambda expression know, it should return a Decorator&&

It is the lambda expression itself is used as an argument for the declared function template parameter Decorator&&.

Finding out the return type of a function, lambda or function

Assuming that:

  1. You want only the return type.
  2. You don't know what are/will be the types of arguments (so neither decltype() nor std::result_of<> is an option.
  3. The functor object passed as argument will not have an overloaded or generic operator().

then you can use the below trait that infers the return type of any functor object:

template <typename F>
struct return_type_impl;

template <typename R, typename... Args>
struct return_type_impl<R(Args...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(Args..., ...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(*)(Args...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(*)(Args..., ...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(&)(Args...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(&)(Args..., ...)> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...)> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...)> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) &> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) &> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) &&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) &&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) volatile&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) volatile&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const volatile&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const volatile&&> { using type = R; };

template <typename T, typename = void>
struct return_type
: return_type_impl<T> {};

template <typename T>
struct return_type<T, decltype(void(&T::operator()))>
: return_type_impl<decltype(&T::operator())> {};

template <typename T>
using return_type_t = typename return_type<T>::type;

Tests:

#include <type_traits>

template <typename F>
void test(F h)
{
static_assert(std::is_same<return_type_t<F>, int>{}, "!");

return_type_t<F> i = 1;
}

int function(int i) { return 2*i; }

int c_varargs_function(...) { return 1; }

struct A
{
int mem_function(double, float) { return 1; }
};

int main()
{
// Function
test(function);

// C-style variadic function
test(c_varargs_function);

// Non-generic lambda
test([](int i) { return 2*i; });

// Member function
test(&A::mem_function);
}

DEMO

how to deduce return type from lambda?

foo takes function pointer while you're passing a lambda, but implicit conversion (from lambda to function pointer) won't be considered in template argument deduction for T, which makes the invocation failing.

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

You can specify template argument explicitly:

foo<int>([]()->int{ return 1; });
// ^^^^^

Or convert lambda to function pointer by operator+ or static_cast.

foo(+[]()->int{ return 1; });
// ^
foo(static_cast<int(*)()>([]()->int{ return 1; }));
// ^^^^^^^^^^^^^^^^^^^^^^ ^

BTW: Omitted parameter list used with trailing return type is a C++23 lambda feature. (So I added ()s.) And as @max66 suggested without trailing return type the lambda works fine too; the return type would be deduced automatically, such as foo(+[]{ return 1; });.

Is it possible to get the type of lambda parameter at runtime?

My first reaction would be “just use intThat(i -> i == 42)”, but apparently, the implementation is dropping the information that it has an ArgumentMatcher<Integer> and later-on relying on the same Reflection approach that doesn’t work.

You can’t get the lambda parameter type reflectively and there are existing Q&As explaining why it’s not possible and not even intended. Note that this is not even lambda specific. There are scenarios with ordinary classes, where the type is not available too.

In the end, there is no point in trying to make this automatism work, when it requires additional declarations on the use side, like the explicit type cast (ArgumentMatcher<Integer>). E.g. instead of

argThat((ArgumentMatcher<Integer>) integer -> integer == 42)

you could also use

argThat(obj -> obj instanceof Integer && (Integer)obj == 42)

or even

argThat(Integer.valueOf(42)::equals)

Though, when we are at these trivial examples, eq(42) would do as well or even when(someInterface.method(42)).thenReturn(42).

However, when you often have to match complex integer expressions in such contexts with broader argument types, a solution close to your original attempt is to declare a reusable fixed type matcher interface

interface IntArgMatcher extends ArgumentMatcher<Integer> {
@Override boolean matches(Integer arg0);
}

and use

when(someInterface.method(argThat((IntArgMatcher)i -> i == 42))).thenReturn(42);

i == 42 being a placeholder for a more complex expression

Returning a lambda function with parameter types constrained by generic type parameters

The wildcard means "of a specific, but unknown type". You can't pass any argument that will satisfy a parameter of type ?, since it may be of the wrong type. Change the return type to ConstrainedBiFunction<Object, String> and it will be able to accept any input type, since every class implicitly extends Object:

public static ConstrainedBiFunction<Object, String> buildBiFxnWithSameTypeArgs() {
return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo);
}

Note that this can still be used for methods with restrictions on the input type, using the PECS principle. For example:

// input parameter must be of type Integer or any supertype,
// so that we can safely pass in an Integer
String combine(ConstrainedBiFunction<? super Integer, String> function) {
return function.apply(1, 2);
}

void test() {
ConstrainedBiFunction<Object, String> concat = buildBiFxnWithSameTypeArgs();
ConstrainedBiFunction<Integer, String> sum = (a, b) -> String.valueOf(a + b);
System.out.println(combine(concat)); // "12"
System.out.println(combine(sum)); // "3"
}

Can we get the type of a lambda argument?

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.]



Related Topics



Leave a reply



Submit