Rozwiązywanie niejednoznacznego przeciążenia wskaźnika funkcji i std :: function dla lambda przy użyciu +


98

W poniższym kodzie pierwsze wywołanie foojest niejednoznaczne i dlatego nie można skompilować.

Drugi, z dodanym +przed lambdą , jest wynikiem przeciążenia wskaźnika funkcji.

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

Co +tu robi notacja?

Odpowiedzi:


103

W +wyrażeniu +[](){}jest +operatorem jednoargumentowym . W [expr.unary.op] / 7 jest zdefiniowany w następujący sposób:

Operand +operatora jednoargumentowego powinien mieć typ arytmetyczny, wyliczeniowy bez zakresu lub typ wskaźnika, a wynikiem jest wartość argumentu.

Lambda nie jest typu arytmetycznego itp., Ale można ją przekonwertować:

[wyr.prim.lambda] / 3

Typ wyrażenia lambda [...] jest unikalnym, nienazwanym typem klasy nieunijnej - zwanym typem zamknięcia - którego właściwości opisano poniżej.

[wyr.prim.lambda] / 6

Typ zamknięcia do lambda ekspresji bez lambda chwytania ma publicNieskarbowych virtualNieskarbowych explicit constfunkcję konwersji do wskaźnika do funkcji o tych samych typów parametrów i powrotnym dla obsługi funkcji danego typu Zamknięcie-tych. 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.

Dlatego jednoargumentowa +wymusza konwersję do typu wskaźnika funkcji, który jest dla tej lambdy void (*)(). Dlatego typ wyrażenia +[](){}to ten typ wskaźnika funkcji void (*)().

Drugie przeciążenie void foo(void (*f)())staje się dokładnym dopasowaniem w rankingu rozpoznawania przeciążenia i w związku z tym jest wybierane jednoznacznie (ponieważ pierwsze przeciążenie NIE jest dokładnym dopasowaniem).


Lambda [](){}można przekonwertować na std::function<void()>za pomocą niejawnego edytora szablonu programu std::function, który przyjmuje dowolny typ, który spełnia wymagania Callablei CopyConstructible.

Lambda można również przekonwertować na void (*)()funkcję konwersji typu zamknięcia (patrz wyżej).

Obie są sekwencjami konwersji zdefiniowanymi przez użytkownika i mają tę samą rangę. Dlatego rozpoznawanie przeciążenia w pierwszym przykładzie zawodzi z powodu niejednoznaczności.


Zdaniem Cassio Neri, popartego argumentem Daniela Krüglera, ta jednoargumentowa +sztuczka powinna być określonym zachowaniem, tj. Można na nim polegać (patrz dyskusja w komentarzach).

Mimo to polecam użycie jawnego rzutowania na typ wskaźnika funkcji, jeśli chcesz uniknąć niejednoznaczności: nie musisz pytać SO, co się dzieje i dlaczego działa;)


3
Wskaźniki funkcji składowej @Fred AFAIK nie mogą być konwertowane na wskaźniki funkcji niebędących członkami, nie mówiąc już o lvalues ​​funkcji. Możesz powiązać funkcję składową za pośrednictwem std::bindz std::functionobiektem, który można wywołać podobnie do funkcji lwartość.
dyp

2
@DyP: Uważam, że możemy polegać na podstępie. Rzeczywiście, załóżmy, że implementacja dodaje operator +()do bezstanowego typu zamknięcia. Załóżmy, że ten operator zwraca coś innego niż wskaźnik do funkcji, na którą konwertuje typ zamknięcia. Wówczas zmieniłoby to obserwowalne zachowanie programu, który narusza 5.1.2 / 3. Proszę, daj mi znać, jeśli zgadzasz się z tym rozumowaniem.
Cassio Neri,

2
@CassioNeri Tak, to jest punkt, w którym nie jestem pewien. Zgadzam się, że obserwowalne zachowanie może się zmienić po dodaniu an operator +, ale jest to porównanie z sytuacją, w której nie ma operator +na początku. Ale nie jest określone, że typ zamknięcia nie powinien mieć operator +przeciążenia. „Implementacja może definiować typ zamknięcia inaczej niż opisano poniżej, pod warunkiem że nie zmienia to obserwowalnego zachowania programu poza [...]”, ale dodanie operatora przez IMO nie zmienia typu zamknięcia na inny niż ten, który jest „opisany poniżej”.
dyp

3
@DyP: Sytuacja, w której nie ma, operator +()jest dokładnie tą opisaną przez normę. Standard pozwala implementacji na zrobienie czegoś innego niż to, co zostało określone. Na przykład dodanie operator +(). Jeśli jednak ta różnica jest zauważalna przez program, to jest to nielegalne. Kiedyś zapytałem w comp.lang.c ++. Moderowane, czy typ zamknięcia może dodać typedef dla result_typei inny typedefswymagany do dostosowania ich (na przykład przez std::not1). Powiedziano mi, że nie może, ponieważ jest to obserwowalne. Spróbuję znaleźć link.
Cassio Neri,

6
VS15 podaje ten zabawny błąd: test.cpp (543): błąd C2593: 'operator +' jest niejednoznaczny t \ test.cpp (543): uwaga: może być 'wbudowany operator C ++ + (void (__cdecl *) (void )) 't \ test.cpp (543): note: or' wbudowany operator C ++ + (void (__stdcall *) (void)) 't \ test.cpp (543): note: or' wbudowany operator C ++ + (void (__fastcall *) (void)) 't \ test.cpp (543): note: lub' wbudowany operator C ++ + (void (__vectorcall *) (void)) 't \ test.cpp (543): note : podczas próby dopasowania listy argumentów '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) test.cpp (543): błąd C2088:' + ': niedozwolone dla klasy
Ed Lambert
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.