Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda traits inconsistency across C++0x compilers

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.

like image 407
Sumant Avatar asked Apr 09 '10 22:04

Sumant


2 Answers

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.

like image 167
Potatoswatter Avatar answered Oct 21 '22 12:10

Potatoswatter


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.

like image 23
Terry Mahaffey Avatar answered Oct 21 '22 12:10

Terry Mahaffey