lambda traits在C ++ 0x编译器中的不一致性

时间:2010-04-09 22:26:21

标签: c++ lambda c++11 traits

我发现两个编译器(g ++ 4.5,VS2010 RC)之间存在一些不一致的方式,它们将lambdas与类模板的部分特化相匹配。我试图为lambdas实现类似boost :: function_types的东西以提取类型特征。查看this了解详情。

在g ++ 4.5中,lambda的operator()的类型看起来像一个独立函数(R(*)(...))的类型,而在VS2010 RC中,它看起来像成员函数(R(C :: *)(...))。所以问题是编译器编写者可以自由地解释他们想要的任何方式吗?如果没有,哪个编译器是正确的?请参阅下面的详细信息。

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 *
}

这个程序在g ++ 4.5和VS2010上编译,但实例化的function_traits是不同的,如代码中所述。

3 个答案:

答案 0 :(得分:4)

我认为海湾合作委员会不合规。N3092§5.1.2/ 5说

  

a的闭包类型   lambda-expression有一个公共内联   函数调用运算符(13.5.4)   参数和返回类型是   由lambda表达式描述   参数声明子句和   尾随 - 返回类型。   这个函数调用运算符是   声明const(9.3.1)当且仅当   lambda表达式   参数声明子句不是   其次是可变的。

因此,虽然关于闭包对象类型的许多内容都是实现定义的,但函数本身必须是public的成员,并且必须是const的非静态成员。

编辑:此程序表明operator()是GCC 4.6上的成员函数,与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;
}

输出:

member of Z4mainEUlvE_, returns i takes i

编辑:看起来唯一的问题是指向某些闭包调用操作符的指针无法匹配ptmf模板模式。解决方法是声明lambda表达式mutable。如果没有捕获,这是没有意义的,只有(除了修复问题)似乎改变了调用操作符的常量。

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;

输出:

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

如果没有mutable和const,spectfun不会打印最后两个查询中的任何一个的签名。

答案 1 :(得分:1)

阅读n3043。 Lambda现在可以转换为函数指针,前提是它们没有任何状态。我相信(......但不知道)GCC最初意外地实施了这种行为,“修复它”,现在将它重新添加到4.5或4.6。 VC10在初始设计时正确实现了lambdas,但不符合n3043的最新工作文件。

答案 2 :(得分:0)

我认为gcc开发人员有充分的理由来表达这种行为。请记住,静态函数没有“this”指针,当实际调用它时,调用者不需要传递“this”指针。因此,当它实际上没有包含在闭包对象中时,这是一个小的性能优化。你可以看到G ++开发人员通过将lambda表达式声明为“可变”来为你提供一种解决方法(记住你实际上没有任何东西可以改变)。