views:

321

answers:

2

I've written a traits class that lets me extract information about the arguments and type of a function or function object in C++0x (tested with gcc 4.5.0). The general case handles function objects:

template <typename F>
struct function_traits {
    template <typename R, typename... A>
    struct _internal { };

    template <typename R, typename... A>
    struct _internal<R (F::*)(A...)> {
        // ...
    };

    typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>;
};

Then I have a specialization for plain functions at global scope:

template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
    // ...
};

This works fine, I can pass a function into the template or a function object and it works properly:

template <typename F>
void foo(F f) {
    typename function_traits<F>::whatever ...;
}

int f(int x) { ... }
foo(f);

What if, instead of passing a function or function object into foo, I want to pass a lambda expression?

foo([](int x) { ... });

The problem here is that neither specialization of function_traits<> applies. The C++0x draft says that the type of the expression is a "unique, unnamed, non-union class type". Demangling the result of calling typeid(...).name() on the expression gives me what appears to be gcc's internal naming convention for the lambda, main::{lambda(int)#1}, not something that syntactically represents a C++ typename.

In short, is there anything I can put into the template here:

template <typename R, typename... A>
struct function_traits<????> { ... }

that will allow this traits class to accept a lambda expression?

+1  A: 

I think it is possible to specialize traits for lambdas and do pattern matching on the signature of the unnamed functor. Here is the code that works on g++ 4.5. Although it works, the pattern matching on lambda appears to be working contrary to the intuition. I've comments inline.

struct X
{
  float operator () (float i) { return i*2; }
  // If the following is enabled, program fails to compile
  // mostly because of ambiguity reasons.
  //double operator () (float i, double d) { return d*f; } 
};

template <typename T>
struct function_traits // matches when T=X or T=lambda
// As expected, lambda creates a "unique, unnamed, non-union class type" 
// so it matches here
{
  // Here is what you are looking for. The type of the member operator()
  // of the lambda is taken and mapped again on function_traits.
  typedef typename function_traits<decltype(&T::operator())>::return_type return_type;
};

// matches for X::operator() but not of lambda::operator()
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...)> 
{
  typedef R return_type;
};

// I initially thought the above defined member function specialization of 
// the trait will match lambdas::operator() because a lambda is a functor.
// It does not, however. Instead, it matches the one below.
// I wonder why? implementation defined?
template <typename R, typename... A>
struct function_traits<R (*)(A...)> // matches for lambda::operator() 
{
  typedef R return_type;
};

template <typename F>
typename function_traits<F>::return_type
foo(F f)
{
  return f(10);
}

template <typename F>
typename function_traits<F>::return_type
bar(F f)
{
  return f(5.0f, 100, 0.34);
}

int f(int x) { return x + x;  }

int main(void)
{
  foo(f);
  foo(X());
  bar([](float f, int l, double d){ return f+l+d; });
}
Sumant
Thanks, that did the trick. It didn't occur to me to just chain the non-specialized template onto the specialized version like that. Once I saw it, it looks incredibly elegant and obvious.
Tony A.
I'm glad it helped. It seems like even though the syntax of getting at the operator() of a lambda is like class's member function, lambda is afterall an anonymous free standing function and matches R (*)(A...) but not R (C::*)(A...).
Sumant
I suppose the reasoning behind it might be that "C" in this case would be an implementation-defined type, and providing access to it through a template argument would allow us to do nonsensical things like define typedef aliases for it. Since we can't do anything with the lambda type except call it like a function, it makes more sense from a language design perspective to force that to be the specialization that applies.
Tony A.
A: 

May I suggest Boost.Function ?

It's a library dedicated to extract type information from a function signature.

It's normally been designed to work with Boost.Bind and Boost.Lambda so we can only imagine they will extend the support to C++0x lambdas... if it's not the case already.

And if it's not, may I suggest you help them out ;) ?

Matthieu M.
How's this relevant? If you implement something like Boost.Function yourself you don't need what the OP is asking for. Boost.Function is not designed to "extract type information from a function signature". What does that even mean? Also, there is no need to extend your Boost.Function library to support C++0x lambdas. They automatically work already.
sellibitze