Який тип лямбда виводиться з "авто" в 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ключовим словом?


8
"Тип лямбда - це покажчик функції" - це було б неефективно і пропустило б цілу точку лямбда.
Конрад Рудольф

Відповіді:


145

Тип виразу лямбда не визначений.

Але вони, як правило, просто синтаксичний цукор для функторів. Лямбда перекладається безпосередньо у функтор. Все, що знаходиться всередині [], перетворюється на параметри конструктора та члени об'єкта функтора, а параметри всередині ()перетворюються на параметри для функтора operator().

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

Але власне тип лямбда не є покажчиком функції. Це якийсь не визначений тип функтора.


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

Це унікальна неназвана структура, яка перевантажує оператора виклику функцій. Кожен екземпляр лямбда вводить новий тип.

У спеціальному випадку лямбда, що не захоплює, структура крім того має неявне перетворення у покажчик функції.


2
Гарна відповідь. Набагато точніше, ніж у мене. +1 :)
jalf

4
+1 для єдиності, спочатку це дуже дивно і заслуговує на увагу.
Матьє М.

Справа не в тому, що це насправді має значення, але чи справді цей тип безіменний, чи йому просто не дано ім’я до моменту компіляції? IOW, чи можна було б використовувати RTTI, щоб знайти ім'я, на яке вирішив компілятор?
Бен

3
@Ben, це неназвано, і що стосується мови C ++, то немає такого поняття, як "ім'я, яке визначає компілятор". Результат визначено type_info::name()реалізацією, тому він може повернути що завгодно. На практиці компілятор назвать тип заради лінкера.
avakar

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]:Тип закриття для лямбда-виразу без відсутності лямбда-захоплення має публічну невіртуальну не явну функцію перетворення const в покажчик на функцію, що має той самий параметр і типи повернення, що і оператор виклику функцій типу закриття. Значення, повернене цією функцією перетворення, є адресою функції, яка при виклику справляє той же ефект, що і виклик оператора виклику функцій типу закриття.

Наслідком цього остаточного уривку є те, що якби ви використали конверсію, ви зможете призначити 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;
}

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


Рекомендую розробити підхід 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.