views:

481

answers:

2

I observed some inconsistency between two compilers (g++ 4.5, VS2010 RC) in the way they match lambdas with partial specializations of class templates. I was trying to implement something like boost::function_types for lambdas to extract type traits. Check this for more details.

In g++ 4.5, the type of the operator() of a lambda appears to be like that of a free standing function (R (*)(...)) whereas in VS2010 RC, it appears to be like that of a member function (R (C::*)(...)). So the question is are compiler writers free to interpret any way they want? If not, which compiler is correct? See the details below.

template <typename T>
struct function_traits 
  : function_traits<decltype(&T::operator())> 
{ 
// This generic template is instantiated on both the compilers as expected.
};

template <typename R, typename C>
struct function_traits<R (C::*)() const>  { // inherits from this one on VS2010 RC
  typedef R result_type;
};

template <typename R>
struct function_traits<R (*)()> { // inherits from this one on g++ 4.5
  typedef R result_type;
};

int main(void) {
  auto lambda = []{};
  function_traits<decltype(lambda)>::result_type *r; // void *
}

This program compiles on both g++ 4.5 and VS2010 but the function_traits that are instantiated are different as noted in the code.

+4  A: 

I believe that GCC is noncompliant. N3092 §5.1.2/5 says

The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose param- eters and return type are described by the lambda-expression’s parameter-declaration-clause and trailing- return-type respectively. This function call operator is declared const (9.3.1) if and only if the lambda- expression’s parameter-declaration-clause is not followed by mutable.

So while many things about the closure object's type are implementation-defined, the function itself must be a member to be public and must be a nonstatic member to be const.

EDIT: This program indicates that operator() is a member function on GCC 4.6, which is essentially the same as 4.5.

#include <iostream>
#include <typeinfo>
using namespace std;

template< class ... > struct print_types {};

template<> struct print_types<> {
 friend ostream &operator<< ( ostream &lhs, print_types const &rhs ) {
  return lhs;
 }
};

template< class H, class ... T > struct print_types<H, T...> {
 friend ostream &operator<< ( ostream &lhs, print_types const &rhs ) {
  lhs << typeid(H).name() << " " << print_types<T...>();
  return lhs;
 }
};

template< class T >
struct spectfun {
 friend ostream &operator<< ( ostream &lhs, spectfun const &rhs ) {
  lhs << "unknown";
  return lhs;
 }
};

template< class R, class ... A >
struct spectfun< R (*)( A ... ) > {
 friend ostream &operator<< ( ostream &lhs, spectfun const &rhs ) {
  lhs << "returns " << print_types<R>()
   << " takes " << print_types<A ...>();
  return lhs;
 }
};

template< class C, class R, class ... A >
struct spectfun< R (C::*)( A ... ) > {
 friend ostream &operator<< ( ostream &lhs, spectfun const &rhs ) {
  lhs << "member of " << print_types<C>() << ", " << spectfun<R (*)(A...)>();
  return lhs;
 }
};

template< class T >
struct getcall {
 typedef decltype(&T::operator()) type;
};

int main() {
 int counter = 0;

 auto count = [=]( int ) mutable { return ++ counter; };

 cerr << spectfun< getcall<decltype(count)>::type >() << endl;
}

output:

member of Z4mainEUlvE_, returns i takes i

EDIT: It looks like the only problem is that pointers to certain closure call operators fail to match ptmf template patterns. The workaround is to declare the lambda expression mutable. This is meaningless if there is no capture and only (aside from fixing the problem) seems to change the const-ness of the call operator.

template< class T >
struct getcall {
    typedef decltype(&T::operator()) type;
    static type const value;
};
template< class T >
typename getcall<T>::type const getcall<T>::value = &T::operator();

int main() {
    auto id = []( int x ) mutable { return x; };
    int (*idp)( int ) = id;
    typedef decltype(id) idt;
    int (idt::*idptmf)( int ) /* const */ = getcall< decltype(id) >::value;

cerr << spectfun< decltype(idp) >() << endl;
cerr << spectfun< decltype(idptmf) >() << endl;
cerr << spectfun< getcall<decltype(id)>::type >() << endl;

output:

returns i takes i 
member of Z4mainEUliE0_ , returns i takes i 
member of Z4mainEUliE0_ , returns i takes i 

Without the mutable and with the const, spectfun does not print signatures for either of the last two queries.

Potatoswatter
Wouldn't that make g++ noncompliant, since it uses free-functions?
GMan
yeah, sounds to me like it's GCC that's getting it wrong.
jalf
@Gman, jalf: Bah, selective hearing. Well, GCC's method involves more friendship… ha!
Potatoswatter
Heh. Well, could it be the case that GCC is making the function static? I think I'm obviously incorrect in stating it's a free function, as a free function has no `T::operator()`. I sadly don't have g++ 4.5 on me to test with. Perhaps making the lambda mutable would force the function to be non-static, because the lambda would carry data.
GMan
@GMan: I'm trying to tease some information out of 4.6. It does correctly implement `mutable` but it looks somewhat like the lambda itself is a function-class, analogous to the class implicitly defined for the function it's within.
Potatoswatter
g++ 4.5 makes any lambda with a capture into expected R (C::*)(...) type. Capture-less lambdas don't appear to behave like that. Although we know based on n3043 (see below) that stateless lambdas are convertible to function pointers, when a better match R(C::*)(...) is present, it should be chosen I think. In that respect g++ is not compliant because it neglects a better match but rightly implements a conversion to R(*)(...). Am I right?
Sumant
@Sumant: Aha, I didn't try one like that! You're right, this program succeeds in extracting `decltype(…::operator())` but doesn't match it to any function type. I'll look into more later.
Potatoswatter
@Sumant: Can you work around the problem entirely by declaring the lambdas `mutable`?
Potatoswatter
@Potatocom: g++ complains saying that capture-less lambdas can't be mutable. BTW, I'm doing this exercise for educational purpose.
Sumant
@Sumant: Mine doesn't, it was updated on April 7. I have no real interest either. Maybe we both should update, lol. Support is much better than it was and nearly complete, which is good to know at least.
Potatoswatter
Sumant
A: 

Read n3043. Lambdas are now convertible to function pointers provided they don't have any state. I believe (...but do not know) GCC initially implemented this behavior accidentally, "fixed it", now will be re-adding it to 4.5 or 4.6. VC10 implemented lambdas correctly as initially designed, but not conforming to the latest working papers with n3043.

Terry Mahaffey
n3092 is the latest draft: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdf [PDF link]
GMan
Yes, it is - but I'm talking about n3043 specifically.
Terry Mahaffey
@Terry: Even though stateless lambdas are convertible to function pointers, when a better match R(C::*)(...) is present, it should be chosen, isn't it?
Sumant
@Sumant, @Terry: I don't have 3043 but 3096 specifies "a public non-virtual non-explicit const conversion function to pointer to function". This is similar to but incompatible with altering the type of the closure object itself. The standard essentially requires *both* a function pointer and a ptmf be available for the call operator. Both are available under GCC but the ptmf does not appear to match templates.
Potatoswatter