15

我观察到两个编译器(g++ 4.5,VS2010 RC)在将 lambdas 与类模板的部分特化匹配的方式上存在一些不一致。我试图为 lambdas 实现类似 boost::function_types 的东西来提取类型特征。检查以获取更多详细信息。

在 g++ 4.5 中,operator()lambda 的类型似乎类似于独立函数 (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 与代码中所述不同。

4

3 回答 3

3

我相信 GCC 是不合规的。N3092 §5.1.2/5 说

lambda 表达式的闭包类型具有公共内联函数调用运算符 (13.5.4),其参数和返回类型分别由 lambda 表达式的参数声明子句和尾随返回类型描述。当且仅当 lambda 表达式的 parameter-declaration-clause 后面没有 mutable 时,此函数调用运算符才被声明为 const (9.3.1)。

因此,虽然关于闭包对象类型的许多事情都是实现定义的,但函数本身必须是成员,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则不会打印最后两个查询中的任何一个的签名。

于 2010-04-09T23:34:53.160 回答
1

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

于 2010-04-10T05:44:12.643 回答
0

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

于 2011-06-25T04:36:26.543 回答