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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
ronag
  • 49,529
  • 25
  • 126
  • 221
  • I was looking at some trick with `std::fuction::first_argument_type`, however VS2010 doesn't seem to implemenet `first_argument_type`. – ronag Jan 03 '12 at 11:58
  • 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::param1_type` or the like. VS2010 has problems with that code though, let me see if I can find my question about it... – Xeo Jan 03 '12 at 12:00
  • You can't use `std::function` for that, as `F` is the lambda type and not a signature like `bool(int)`. – Xeo Jan 03 '12 at 12:03
  • 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? – pmr Jan 03 '12 at 12:09
  • 1
    Because filter has a method e.g. `operator()(T)` which needs to know the argument type. – ronag Jan 03 '12 at 12:13
  • @ronag: It would be the easiest if you just made that a template too and simply forward. – Xeo Jan 03 '12 at 12:13
  • @ronag: `template bool operator()(Arg&& arg){ _f(std::forward(arg)); }` where `_f` is the stored function. – Xeo Jan 03 '12 at 12:18
  • Updated question to make more sense. – ronag Jan 03 '12 at 12:18

1 Answers1

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; });
}
Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Great! However, what about if filter::operator() is virtual? – ronag Jan 03 '12 at 12:23
  • @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? – Xeo Jan 03 '12 at 12:25
  • Because this is a simplified example, and in my real code filter has a base class. – ronag Jan 03 '12 at 12:26
  • @ronag: Let me edit in a sec to show you a possible solution. – Xeo Jan 03 '12 at 12:28
  • 2
    @ronag: Came to the conclusion that you're basically screwed with MSVC10. :| – Xeo Jan 03 '12 at 12:49
  • 1
    @ronag: FWIW, I finally came around to actually submitting [a bug report to Microsoft](https://connect.microsoft.com/VisualStudio/feedback/details/716358/decltype-of-a-member-of-a-template-parameter-not-considered-a-dependant-name#details). – Xeo Jan 03 '12 at 13:45
  • Does the generated lambdax classes have any hidden typedefs one might be able to use? – ronag Jan 03 '12 at 13:45
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/6352/discussion-between-ronag-and-xeo) – ronag Jan 03 '12 at 14:14
  • @ronag: `typename function_traits::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. – Xeo Jan 03 '12 at 14:19
  • Updated my question with my implementation of your solution. – ronag Jan 03 '12 at 17:14
  • @ronag: You could atleast typedef the parameter type inside the derived class. :P And factor that `base<...>` thing out into a simple `get_base` template that hides away the nastiness. – Xeo Jan 03 '12 at 17:31
  • @ronag: Why do you ask? I already told you in the chat that you can't return a function type from a function. – Xeo Jan 03 '12 at 18:02
  • Nvm, I missed the ::type part. – ronag Jan 03 '12 at 18:04