Лямбда черты несоответствия в компиляторах C ++ 0x - PullRequest
15 голосов
/ 10 апреля 2010

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

Ответы [ 3 ]

4 голосов
/ 10 апреля 2010

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

Тип закрытия для лямбда-выражение имеет публичный встроенный оператор вызова функции (13.5.4), чей параметры и тип возвращаемого значения описывается лямбда-выражением параметр-объявление-предложение и трейлинг-возврат-тип соответственно. Этот оператор вызова функции объявлен 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. Это бессмысленно, если нет захвата, и только (не считая исправления проблемы), по-видимому, изменяет постоянство оператора вызова.

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 

Без переменной и с const, spectfun не печатает подписи ни для одного из последних двух запросов.

1 голос
/ 10 апреля 2010

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

0 голосов
/ 25 июня 2011

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

...