Какого типа лямбда-выражения выводятся с помощью «auto» в C ++ 11?

142

У меня было ощущение, что тип лямбда - это указатель на функцию. Когда я выполнил следующий тест, я обнаружил, что это неправильно ( демо ).

#define LAMBDA [] (int i) -> long { return 0; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok
  assert(typeid(pFptr) == typeid(pAuto));  // assertion fails !
}

Выше кода не хватает какой-либо точки? Если нет, то каково typeofлямбда-выражение при выводе с autoключевым словом?

iammilind
источник
8
«Тип лямбды - это указатель на функцию» - это было бы неэффективно и пропустило бы весь смысл лямбды.
Конрад Рудольф

Ответы:

145

Тип лямбда-выражения не указан.

Но они, как правило, просто синтаксический сахар для функторов. Лямбда переводится прямо в функтор. Все, что внутри [], превращается в параметры конструктора и члены объекта функтора, а параметры внутри ()превращаются в параметры для функтора operator().

Лямбда, которая не содержит переменных (ничего внутри []), может быть преобразована в указатель на функцию (MSVC2010 не поддерживает это, если это ваш компилятор, но это преобразование является частью стандарта).

Но фактический тип лямбды не является указателем на функцию. Это некоторый неопределенный тип функтора.

jalf
источник
1
MSVC2010 не поддерживает преобразование в указатель на функцию, но MSVC11 поддерживает. blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
KindDragon
18
+1 за «простой синтаксический сахар для функторов». Помня об этом, можно избежать большой путаницы.
Бен
4
функтор - это что угодно, в operator()основном это stackoverflow.com/questions/356950/c-functors-and-their-uses
TankorSmash
107

Это уникальная безымянная структура, которая перегружает оператор вызова функции. Каждый экземпляр лямбды вводит новый тип.

В особом случае не захвата лямбды, структура дополнительно имеет неявное преобразование в указатель на функцию.

avakar
источник
2
Хороший ответ. Гораздо точнее, чем у меня. +1 :)
полдня
4
+1 для уникальности, сначала это очень удивительно и заслуживает внимания.
Матье М.
Не то, чтобы это действительно имело значение, но действительно ли тип безымянный, или ему просто не дали имя до времени компиляции? Можно ли использовать RTTI, чтобы найти имя, на которое определил компилятор?
Бен
3
@Ben, он безымянный, и что касается языка C ++, не существует такого понятия, как «имя, которое определяет компилятор». Результат type_info::name()определяется реализацией, поэтому может возвращать что угодно. На практике компилятор назовет тип для компоновщика.
Авакар
1
В последнее время, когда мне задают этот вопрос, я обычно говорю, что у лямбда-типа есть имя, его знает компилятор, оно просто невыразимо.
Андре Костур
24

[C++11: 5.1.2/3]: Тип лямбда-выражения (который также является типом объекта замыкания) является уникальным безымянным типом класса без объединения, называемым типом замыкания , свойства которого описаны ниже. Этот тип класса не является совокупным (8.5.1). Тип замыкания объявляется в самой маленькой области блока, области класса или области пространства имен, которая содержит соответствующее лямбда-выражение . [..]

Предложение продолжает перечислять различные свойства этого типа. Вот некоторые основные моменты:

[C++11: 5.1.2/5]:Тип замыкания для лямбда-выражения имеет открытый inlineоператор вызова функции (13.5.4), параметры и тип возвращаемого значения которого описываются с помощью параметра-объявления- выражения лямбда-выражения и трейлинг-возврата-типа соответственно. [..]

[C++11: 5.1.2/6]:Тип замыкания для лямбда-выражения без лямбда-захвата имеет открытую не виртуальную неявную функцию преобразования констант в указатель на функцию, имеющую тот же параметр и возвращаемые типы, что и оператор вызова функции типа замыкания. Значение, возвращаемое этой функцией преобразования, должно быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции типа замыкания.

Следствием этого последнего отрывка является то, что, если вы использовали преобразование, вы сможете назначить LAMBDAдля pFptr.

Гонки легкости на орбите
источник
3
#include <iostream>
#include <typeinfo>

#define LAMBDA [] (int i)->long { return 0l; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok

  std::cout<<typeid( *pAuto ).name() << std::endl;
  std::cout<<typeid( *pFptr ).name() << std::endl;

  std::cout<<typeid( pAuto ).name() << std::endl;
  std::cout<<typeid( pFptr ).name() << std::endl;
}

Типы функций действительно одинаковы, но лямбда вводит новый тип (например, функтор).

BЈовић
источник
Я рекомендую CXXABI, если вы уже идете по этому пути. Вместо этого я обычно использую __PRETTY_FUNCTION__, как в template<class T> const char* pretty(T && t) { return __PRETTY_FUNCTION__; }, и снимаю лишнее, если оно начинает переполняться. Я предпочитаю видеть шаги, показанные при замене шаблона. Если вам не хватает __PRETTY_FUNCTION__, есть альтернативы для MSVC и т. Д., Но результаты всегда зависят от компилятора по той же причине, по которой необходим CXXABI.
Джон П
1

Следует также отметить, что лямбда-преобразователь является указателем на функцию. Однако typeid <> возвращает нетривиальный объект, который должен отличаться от лямбды до указателя обобщенной функции. Таким образом, тест для typeid <> не является допустимым предположением. В целом C ++ 11 не хочет, чтобы мы беспокоились о спецификации типа, все это имеет значение, если данный тип может быть преобразован в целевой тип.

Сайед Райхан
источник
Это справедливо, но типы печати имеют большое значение для получения правильного типа, не говоря уже о выявлении случаев, когда тип является конвертируемым, но не удовлетворяет другим ограничениям. (Я бы всегда старался «ограничивать» ограничения везде, где это возможно, но у кого-то, пытающегося это сделать, есть все больше причин демонстрировать свою работу во время разработки.)
Джон П.
0

Практическое решение из Как я могу сохранить объект boost :: bind в качестве члена класса? , попробуйте boost::function<void(int)>или std::function<void(int)>.

Габриель
источник
1
Однако следует учитывать затраты на производительность (вызов виртуальной функции происходит каждый раз, когда вызывается функция).
HighCommander4