Reputation: 4326
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.
Upvotes: 15
Views: 1689
Reputation: 10436
I think gcc developers has a good reason for this behaivor. Remember, a static function do not have a "this" pointer, and when it is being actually called, the caller do not required to pass the "this" pointer. So this is a small performance optimisation when it is actually nothing contained in the closure object. And you can see the G++ developer leave you a way to workaround by declaring the lambda expression as "mutable" (remember you actually do not have anything to mutate).
Upvotes: 0
Reputation: 137810
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.
Upvotes: 3
Reputation: 11981
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.
Upvotes: 1