15

2 つのコンパイラ (g++ 4.5、VS2010 RC) の間で、ラムダとクラス テンプレートの部分的な特殊化を一致させる方法に矛盾があることがわかりました。タイプの特徴を抽出するために、ラムダのboost::function_typesのようなものを実装しようとしていました。詳細については、これを確認してください。

g++ 4.5 では、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 はコードに示されているように異なります。

4

3 に答える 3

3

GCC は準拠していないと思います。N3092 §5.1.2/5 は言う

ラムダ式のクロージャー型には、パブリック インライン関数呼び出し演算子 (13.5.4) があり、そのパラメーターと戻り値の型は、ラムダ式の parameter-declaration-clause と Trailing-return-type によってそれぞれ記述されます。この関数呼び出し演算子は、ラムダ式の 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 テンプレート パターンと一致しないことです。回避策は、ラムダ式を宣言することmutableです。これは、キャプチャがなく、(問題の修正は別として) 呼び出し演算子の const-ness を変更するだけの場合は無意味です。

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 がなく、constspectfunがある場合、最後の 2 つのクエリのいずれの署名も出力しません。

于 2010-04-09T23:34:53.160 に答える
1

n3043を読んでください。状態を持たないラムダは、関数ポインタに変換できるようになりました。私は信じています(...しかし知りません)GCCは最初にこの動作を誤って実装し、「修正」しましたが、現在は4.5または4.6に再度追加する予定です。VC10 はラムダを最初の設計どおりに正しく実装しましたが、n3043 の最新のワーキング ペーパーには準拠していません。

于 2010-04-10T05:44:12.643 に答える
0

gcc開発者には、この行動の正当な理由があると思います。静的関数には「this」ポインターがなく、実際に呼び出されるときに、呼び出し元が「this」ポインターを渡す必要がないことを忘れないでください。したがって、これは、実際にはクロージャオブジェクトに何も含まれていない場合の、小さなパフォーマンスの最適化です。また、G ++開発者は、ラムダ式を「可変」として宣言することで回避策を残していることがわかります(実際には、変更するものがないことを忘れないでください)。

于 2011-06-25T04:36:26.543 に答える