Jaki jest typ lambda, gdy jest wydedukowana za pomocą „auto” w C ++ 11?

142

Miałem wrażenie, że typ lambda to wskaźnik funkcji. Kiedy wykonałem następujący test, stwierdziłem, że jest źle ( demo ).

#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 !
}

Czy w powyższym kodzie brakuje jakiegoś punktu? Jeśli nie, to jakie jest typeofwyrażenie lambda po wydedukowaniu za pomocą autosłowa kluczowego?

iammilind
źródło
8
„Typ lambda to wskaźnik funkcji” - byłoby to nieefektywne i pomijałoby cały sens lambd.
Konrad Rudolph

Odpowiedzi:

145

Typ wyrażenia lambda jest nieokreślony.

Ale generalnie są one tylko cukrem syntaktycznym dla funktorów. Lambda jest tłumaczona bezpośrednio na funktor. Wszystko w środku []jest zamieniane na parametry konstruktora i składowe obiektu funktora, a parametry wewnątrz ()są zamieniane na parametry dla funktora operator().

Lambda, która nie przechwytuje żadnych zmiennych (nic wewnątrz []'s), może zostać przekonwertowana na wskaźnik funkcji (MSVC2010 nie obsługuje tego, jeśli jest to twój kompilator, ale ta konwersja jest częścią standardu).

Ale rzeczywisty typ lambda nie jest wskaźnikiem funkcji. To jakiś nieokreślony typ funktora.

jalf
źródło
1
MSVC2010 nie obsługuje konwersji na wskaźnik funkcji, ale MSVC11 tak. blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
KindDragon
18
+1 za „zwykły cukier syntaktyczny dla funktorów”. Pamiętając o tym, można uniknąć wielu potencjalnych nieporozumień.
Ben
4
funktor to wszystko, w operator()zasadzie stackoverflow.com/questions/356950/c-functors-and-their-uses
TankorSmash
107

Jest to unikalna nienazwana struktura, która przeciąża operator wywołania funkcji. Każde wystąpienie lambda wprowadza nowy typ.

W szczególnym przypadku lambdy nieprzechwytywanej struktura dodatkowo ma niejawną konwersję na wskaźnik funkcji.

avakar
źródło
2
Niezła odpowiedź. O wiele bardziej precyzyjny niż mój. +1 :)
jalf
4
+1 za część jednorazową, na początku jest to bardzo zaskakujące i zasługuje na uwagę.
Matthieu M.
Nie żeby to naprawdę miało znaczenie, ale czy typ jest naprawdę nienazwany, czy po prostu nie nadano mu nazwy do czasu kompilacji? IOW, czy można użyć RTTI, aby znaleźć nazwę, na którą zdecydował się kompilator?
Ben
3
@Ben, nie ma nazwy i jeśli chodzi o język C ++, nie ma czegoś takiego jak „nazwa, na której decyduje kompilator”. Wynik type_info::name()jest zdefiniowany w implementacji, więc może zwrócić wszystko. W praktyce kompilator nada typowi nazwę na potrzeby konsolidatora.
avakar
1
Ostatnio, kiedy zadawałem to pytanie, zwykle mówię, że typ lambdy ma nazwę, kompilator ją zna, jest po prostu niewypowiedziana.
Andre Kostur
24

[C++11: 5.1.2/3]: Typ wyrażenia lambda (będącego również typem obiektu zamknięcia) jest unikalnym, nienazwanym typem klasy nieunijnej - zwanym typem zamknięcia - którego właściwości opisano poniżej. Ten typ klasy nie jest agregatem (8.5.1). Typ zamknięcia jest zadeklarowany w najmniejszym zakresie blokowym, zakresie klasy lub zakresie przestrzeni nazw, który zawiera odpowiednie wyrażenie lambda . [..]

Klauzula zawiera listę różnych właściwości tego typu. Oto kilka najważniejszych informacji:

[C++11: 5.1.2/5]:Typ zamknięcia do lambda ekspresji z publicznego inlinedla obsługi funkcji (13.5.4), którego parametry i rodzaj powrotu opisano przez lambda ekspresji „S -parametrów zgłoszenia klauzula i spływu zwrotny typu odpowiednio. [..]

[C++11: 5.1.2/6]:Typ zamknięcia dla wyrażenia lambda bez przechwytywania lambda ma publiczną niewirtualną, niejawną funkcję konwersji const na wskaźnik do funkcji mającej ten sam parametr i typy zwracane, co operator wywołania funkcji typu zamknięcia. Wartość zwracana przez tę funkcję konwersji będzie adresem funkcji, która po wywołaniu ma taki sam skutek, jak wywołanie operatora wywołania funkcji typu zamknięcia.

Konsekwencją tego ostatniego fragmentu jest to, że gdybyś użył konwersji, byłbyś w stanie przypisać LAMBDAdo pFptr.

Lekkość wyścigów na orbicie
źródło
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;
}

Typy funkcji są rzeczywiście takie same, ale lambda wprowadza nowy typ (jak funktor).

BЈовић
źródło
Polecam podejście rozplątywania CXXABI, jeśli już jedziesz tą trasą. Zamiast tego zwykle używam __PRETTY_FUNCTION__, jak w template<class T> const char* pretty(T && t) { return __PRETTY_FUNCTION__; }, i zdejmuję dodatkowe, jeśli zacznie się robić tłoczno. Wolę zobaczyć kroki pokazane w zastępowaniu szablonów. Jeśli brakuje __PRETTY_FUNCTION__, są alternatywy dla MSVC itp., Ale wyniki są zawsze zależne od kompilatora z tego samego powodu, dla którego CXXABI jest konieczny.
John P
1

Należy również zauważyć, że lambda można zamienić na wskaźnik funkcji. Jednak typeid <> zwraca obiekt inny niż trvial, który powinien różnić się od lambda do ogólnego wskaźnika funkcji. Zatem test dla typeid <> nie jest prawidłowym założeniem. Ogólnie C ++ 11 nie chce, abyśmy martwili się o specyfikację typu, wszystko to ma znaczenie, jeśli dany typ można zamienić na typ docelowy.

Syed Raihan
źródło
To sprawiedliwe, ale drukowane typy znacznie ułatwiają uzyskanie właściwego typu, nie wspominając o wychwyceniu przypadków, w których typ jest wymienialny, ale nie spełnia innych ograniczeń. (Zawsze dążyłbym do „reifikacji” ograniczeń, gdziekolwiek to możliwe, ale ktoś, kto próbuje to zrobić, ma tym więcej powodów, aby pokazać swoją pracę podczas tworzenia.)
John P