27

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?

Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
Tony Allevato
  • 6,429
  • 1
  • 29
  • 34
  • No. Why do you think you need something like this? – sellibitze Apr 01 '10 at 18:10
  • 2
    I thought my example gave a decent use case: If I have a generic algorithm that takes in a function or function object, I can use this traits class to determine not only the return type (which could also be done with decltype nowadays), but also the types of the arguments. (I left out the bulk of the code to keep the post from being too long.) Since I can pass in a function or function object, for orthogonality purposes I'd like to be able to pass in a lambda as well. This is all basically an academic exercise that arose from reading "Elements of Programming". – Tony Allevato Apr 01 '10 at 18:23
  • @Tony: The answer is yes, I've done it. I'll be able to get back to this question a bit later, though. What traits are you trying to get? – GManNickG Apr 01 '10 at 19:13
  • I should clarify: yes depending on what you want. – GManNickG Apr 01 '10 at 19:25
  • Basically I'm interested in the type of the function result and the types/number of its arguments; the things represented by R and A... in the template above. I just played around with it some more and a lambda of the form [](int x) { return x; } can be explicitly cast to a regular function pointer int(*)(int), so it seems like there should be a way to use that to my advantage. I just need to make it work in conjunction with the other two versions of function_traits, especially the one for function objects that doesn't specialize on its argument. – Tony Allevato Apr 01 '10 at 21:39
  • @Tony: On second though, I don't think what you want is possible. I'll think about it more, but I revoke my claim of it being possible. :) – GManNickG Apr 02 '10 at 03:03
  • Note, there might not be *single* correct "type" for a function object due to overloading on operator() and/or using member templates for those. I would even say that it's generally a bad idea to rely on function object types that only have a single non-templated function call operator. – sellibitze Apr 02 '10 at 10:37

3 Answers3

20

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
  • 4,286
  • 1
  • 23
  • 31
  • 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 Allevato Apr 02 '10 at 11:53
  • 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 Apr 02 '10 at 15:25
  • 2
    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 Allevato Apr 02 '10 at 19:04
  • Try it with a captured variable in the [] too, since (n3333 std draft) section 5.1.2 Lambda Expressions [expr.prim.lambda] "6. The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator." – idupree Jun 07 '12 at 06:44
  • 3
    *(I deleted my comment accidentally, here it is again):* The `decltype(&T::operator())` of a lambda (capture, or non-capture) will match on `template struct function_traits ` but not `template struct function_traits`. Note the `const` near the end. Basically, don't forget cv-qualifiers on `*this`. For more, see http://stackoverflow.com/questions/25654186/trait-to-drop-const-from-a-member-function-type – Aaron McDaid Sep 03 '14 at 23:03
  • @AaronMcDaid: Thanks! I had to add another specialization with `const` as you mentioned in order to get this code example to compile. And yes @idupree, this also works with closures. Awesome! – Bob Kocisko Sep 22 '16 at 13:27
  • The code in the answer the moment doesn't even compile, and I think it has a number of errors in the comments. I'm testing the code now and seeing how to fix it so that it actually works – Aaron McDaid Sep 22 '16 at 17:24
  • How I can use this traits inside a function template, that receives a lambda like void func(T lambda){} to declare a std::function inside? – barney Apr 22 '17 at 14:29
7

The void_t trick can help. How does `void_t` work?

Unless you have C++17, you'll need to include the definition of void_t:

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

Add an extra template argument to the original template, defaulted to void:

template <typename T, typename = void>
struct function_traits;

The traits object for simple functions is the same as you already have:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

For non-const methods:

template <typename R, typename... A>
struct function_traits<R (C::*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

Don't forget const methods:

template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...) const> // const
{
    using return_type = R;
    using class_type  = C;
    using args_type   = std:: tuple< A... >;
};

Finally, the important trait. Given a class type, including lambda types, we want to forward from T to decltype(&T::operator()). We want to ensure that this trait is only available for types T for which ::operator() is available, and this is what void_t does for us. To enforce this constraint, we need to put &T::operator() into the trait signature somewhere, hence template <typename T> struct function_traits<T, void_t< decltype(&T::operator())

template <typename T>
struct   function_traits<T, void_t< decltype(&T::operator()) > > 
: public function_traits<           decltype(&T::operator())   >
{
};

The operator() method in (non-mutable, non-generic) lambdas is const, which explains why we need the const template above.

But ultimately this is very restrictive. This won't work with generic lambdas, or objects with templated operator(). If you reconsider your design, you find find a different approach that is more flexible.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
1

By delegating some of the work to a series of function templates instead of a class template, you can extract the relevant info.

First though, I should say that the relevant method is a const method, for a lambda (for a non-capturing, non-generic, non-mutable lambda). So you will not be able to tell the difference between a true lambda and this:

struct {
    int operator() (int) const { return 7; }
} object_of_unnamed_name_and_with_suitable_method;

Therefore, I must assume that you don't want "special treatment" for lambdas, and you don't want to test if a type is a lambda type, and that instead you want to simply extract the return type, and the type of all arguments, for any object which is simple enough. By "simple enough" I mean, for example, that the operator() method is not itself a template. And, for bonus information, a boolean to tell us if an operator() method was present and used, as opposed to a plain old function.



// First, a convenient struct in which to store all the results:
template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args>
struct function_traits_results {
    constexpr static bool is_method = is_method_;
    constexpr static bool is_const_method = is_const_method_;
    typedef C class_type; // void for plain functions. Otherwise,
                          // the functor/lambda type
    typedef R return_type;
    typedef tuple<Args...> args_type_as_tuple;
};

// This will extract all the details from a method-signature:
template<typename>
struct intermediate_step;
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...)>  // non-const methods
    : public function_traits_results<true, false, C, R, Args...>
{
};
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...) const> // const methods
    : public function_traits_results<true, true, C, R, Args...>
{
};


// These next two overloads do the initial task of separating
// plain function pointers for functors with ::operator()
template<typename R, typename ...Args>
function_traits_results<false, false, void, R, Args...>
function_traits_helper(R (*) (Args...) );
template<typename F, typename ..., typename MemberType = decltype(&F::operator()) >
intermediate_step<MemberType>
function_traits_helper(F);


// Finally, the actual `function_traits` struct, that delegates
// everything to the helper
template <typename T>
struct function_traits : public decltype(function_traits_helper( declval<T>() ) )
{
};
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • How I can use this traits inside a function template, that receives a lambda like void func(T lambda){} to declare a std::function inside? – barney Apr 22 '17 at 14:28