Несоответствие признаков лямбда для компиляторов С++ 0x

Я заметил некоторую несогласованность между двумя компиляторами (g++ 4.5, VS2010 RC) в том, как они соответствуют lambdas с частичной специализацией шаблонов классов. Я пытался реализовать что-то вроде boost:: function_types для lambdas для извлечения черт типа. Подробнее см. .

В 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 различаются, как указано в коде.

Ответы

Ответ 1

Я считаю, что GCC несовместим. N3092 §5.1.2/5 говорит

Тип замыкания для лямбда-выражение имеет общедоступную встроенную оператор функции вызова (13.5.4) параметры и тип возврата описываемых лямбда-выражениями параметр-объявление-предложение и trailing-return-type соответственно. Этот оператор вызова функции объявленный const (9.3.1) тогда и только тогда, когда лямбда-выражения Параметр-выражение-предложение не с последующим изменением.

Таким образом, хотя многие вещи о типе объекта замыкания определяются по реализации, сама функция должна быть членом public и должна быть нестатическим членом, чтобы быть const.

EDIT: Эта программа указывает, что 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

EDIT: Похоже, единственная проблема заключается в том, что указатели на некоторые операторы вызова замыкания не соответствуют шаблонам шаблонов ptmf. Обходным путем является объявление лямбда-выражения 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 и с константой spectfun не печатает подписи для любого из двух последних запросов.

Ответ 2

Прочитайте n3043. Lambdas теперь конвертируются в указатели функций, если они не имеют никакого состояния. Я верю (... но не знаю) GCC изначально реализовал это поведение случайно, "исправил его", теперь будет добавлять его к 4.5 или 4.6. VC10 реализовал lambdas правильно, как первоначально разработан, но не соответствует последним рабочим документам с n3043.

Ответ 3

Я думаю, что у разработчиков gcc есть веская причина для этого поведения. Помните, что статическая функция не имеет указателя "this", и когда она фактически вызывается, вызывающему абоненту не требуется передавать указатель "this". Таким образом, это небольшая оптимизация производительности, когда на самом деле ничего не содержится в объекте закрытия. И вы можете видеть, что разработчик g++ оставляет вам способ обхода путем объявления выражения лямбда как "изменчивого" (помните, что на самом деле у вас нет ничего, чтобы мутировать).