Get lambda parameter type
Asked Answered
H

1

13

I would like some way to get the first parameter type of a lambda function, is this possible?

e.g.

instead of:

template<typename T>
struct base
{
     virtual bool operator()(T) = 0;
}

template<typename F, typename T>
struct filter : public base<T>
{
     virtual bool operator()(T) override {return /*...*/ }
};

template<typename T, typename F>
filter<T> make_filter(F func)
{
      return filter<F, T>(std::move(func));
}

auto f = make_filter<int>([](int n){return n % 2 == 0;});

I would like:

template<typename F>
struct filter : public base<typename param1<F>::type>
{
     bool operator()(typename param1<F>::type){return /*...*/ }
};

template<typename F>
filter<F> make_filter(F func)
{
      return filter<F>(std::move(func));
}

auto f = make_filter([](int n){return n % 2 == 0;});

Based on Xeo's answer this is what I got working in VS2010:

template<typename FPtr>
struct arg1_traits_impl;

template<typename R, typename C, typename A1>
struct arg1_traits_impl<R (C::*)(A1)>{typedef A1 arg1_type;};

template<typename R, typename C, typename A1>
struct arg1_traits_impl<R (C::*)(A1) const>{typedef A1 arg1_type;};

template<typename T>
typename arg1_traits_impl<T>::arg1_type arg1_type_helper(T);

template<typename F>
struct filter : public base<typename std::decay<decltype(detail::arg1_type_helper(&F::operator()))>::type>
{
    bool operator()(typename std::decay<decltype(detail::arg1_type_helper(&F::operator()))>::type){return /*...*/ }
};

template<typename T, typename F>
filter<F> make_filter(F func)
{
      return filter<F>(std::move(func));
}

I've tried simplifying the the code, but any attempt seems to break it.

Hygrophilous answered 3/1, 2012 at 11:48 Comment(8)
I was looking at some trick with std::fuction::first_argument_type, however VS2010 doesn't seem to implemenet first_argument_type.Hygrophilous
Complicated. Very complicated, especially without variadic templates and with VS2010. You'll need some kind of function-traits that splits the actual type of a function pointer down to its components and use function_traits<decltype(&F::operator())>::param1_type or the like. VS2010 has problems with that code though, let me see if I can find my question about it...Eleaseeleatic
You can't use std::function<F> for that, as F is the lambda type and not a signature like bool(int).Eleaseeleatic
I'm curious: Why does filter need to know the arguments to the function? Can't you just write a generic perfect forwarding function with variadic arguments?Indistinguishable
Because filter has a method e.g. operator()(T) which needs to know the argument type.Hygrophilous
@ronag: It would be the easiest if you just made that a template too and simply forward.Eleaseeleatic
@ronag: template<class Arg> bool operator()(Arg&& arg){ _f(std::forward<Arg>(arg)); } where _f is the stored function.Eleaseeleatic
Updated question to make more sense.Hygrophilous
E
9

The easiest option would be to just make the operator() a template itself:

template<typename F>
struct filter
{
     template<class Arg>
     void operator(Arg&& arg){
       // use std::forward<Arg>(arg) to call the stored function
     }
};

template<typename F>
filter<F> make_filter(F func)
{
      return filter<F>(std::move(func));
}

auto f = make_filter([](int n){return n % 2 == 0;});

Now, theoretically, the following code should just work. However, it doesn't with MSVC10 thanks to a bug:

#include <iostream>
#include <typeinfo>
 
template<class FPtr>
struct function_traits;
 
template<class T, class C>
struct function_traits<T (C::*)>
{
    typedef T type;
};
 
template<class F>
void bar(F f){
  typedef typename function_traits<
      decltype(&F::operator())>::type signature;
  std::cout << typeid(signature).name();
}
 
int main(){
    bar([](int n){ return n % 2 == 0; });
}

Here's an example on how it would look with GCC. MSVC10, however, simply doesn't compile the code. See this question of mine for further detail. Basically, MSVC10 doesn't treat decltype(&F::operator()) as a dependent type. Here's a work-around that was devised in a chat discussion:

#include <iostream>
#include <typeinfo>
#include <type_traits>

template<class FPtr>
struct function_traits;

template<class R, class C, class A1>
struct function_traits<R (C::*)(A1)>
{   // non-const specialization
    typedef A1 arg_type;
    typedef R result_type;
    typedef R type(A1);
};

template<class R, class C, class A1>
struct function_traits<R (C::*)(A1) const>
{   // const specialization
    typedef A1 arg_type;
    typedef R result_type;
    typedef R type(A1);
};

template<class T>
typename function_traits<T>::type* bar_helper(T);

template<class F>
void bar(F f){
  typedef decltype(bar_helper(&F::operator())) fptr;
  typedef typename std::remove_pointer<fptr>::type signature;
  std::cout << typeid(signature).name();
}

int main(){
    bar([](int n){ return n % 2 == 0; });
}
Eleaseeleatic answered 3/1, 2012 at 12:20 Comment(13)
Great! However, what about if filter::operator() is virtual?Hygrophilous
@ronag: Then you got a problem. :| Why would it need to be virtual if the class itself is templated on the function / functor type anyways?Eleaseeleatic
Because this is a simplified example, and in my real code filter has a base class.Hygrophilous
@ronag: Let me edit in a sec to show you a possible solution.Eleaseeleatic
@ronag: Came to the conclusion that you're basically screwed with MSVC10. :|Eleaseeleatic
@ronag: FWIW, I finally came around to actually submitting a bug report to Microsoft.Eleaseeleatic
Does the generated lambdax classes have any hidden typedefs one might be able to use?Hygrophilous
let us continue this discussion in chatHygrophilous
@ronag: typename function_traits<U>::type would be a function type (bool(int) in this case). Functions can't return other functions, so because the substitution would create an illegal type, the function gets SFINAE'd out instead.Eleaseeleatic
Updated my question with my implementation of your solution.Hygrophilous
@ronag: You could atleast typedef the parameter type inside the derived class. :P And factor that base<...> thing out into a simple get_base<F> template that hides away the nastiness.Eleaseeleatic
@ronag: Why do you ask? I already told you in the chat that you can't return a function type from a function.Eleaseeleatic
Nvm, I missed the ::type part.Hygrophilous

© 2022 - 2024 — McMap. All rights reserved.