Risoluzione dell'overload ambiguo sul puntatore a funzione e std :: function per un lambda utilizzando +


93

Nel codice seguente, la prima chiamata a fooè ambigua e pertanto la compilazione non riesce.

Il secondo, con l'aggiunta +prima del lambda, risolve l'overload del puntatore alla funzione.

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

Cosa ci fa la +notazione qui?

Risposte:


98

L' +espressione +[](){}è l' +operatore unario . È definito come segue in [expr.unary.op] / 7:

L'operando dell'operatore unario +deve avere un'enumerazione aritmetica, senza ambito o un tipo di puntatore e il risultato è il valore dell'argomento.

La lambda non è di tipo aritmetico ecc., Ma può essere convertita:

[expr.prim.lambda] / 3

Il tipo di espressione lambda [...] è un tipo di classe non union univoco e senza nome, chiamato tipo di chiusura , le cui proprietà sono descritte di seguito.

[expr.prim.lambda] / 6

Il tipo di chiusura per una lambda espressione senza lambda-cattura ha una publicnon virtualnon explicit constfunzione di conversione di puntatore alla funzione avente gli stessi tipi di parametri e di ritorno come operatore chiamata di funzione del tipo di chiusura. Il valore restituito da questa funzione di conversione deve essere l'indirizzo di una funzione che, quando invocata, ha lo stesso effetto dell'invocazione dell'operatore di chiamata di funzione del tipo di chiusura.

Pertanto, l'unario +forza la conversione al tipo di puntatore a funzione, che è per questo lambda void (*)(). Pertanto, il tipo di espressione +[](){}è questo tipo di puntatore a funzione void (*)().

Il secondo sovraccarico void foo(void (*f)())diventa una corrispondenza esatta nella classifica per la risoluzione del sovraccarico e viene quindi scelto in modo univoco (poiché il primo sovraccarico NON è una corrispondenza esatta).


Il lambda [](){}può essere convertito std::function<void()>tramite il template ctor non esplicito di std::function, che accetta qualsiasi tipo che soddisfi i requisiti Callablee CopyConstructible.

La lambda può anche essere convertita void (*)()tramite la funzione di conversione del tipo di chiusura (vedi sopra).

Entrambe sono sequenze di conversione definite dall'utente e dello stesso rango. Ecco perché la risoluzione del sovraccarico non riesce nel primo esempio a causa dell'ambiguità.


Secondo Cassio Neri, supportato da un argomento di Daniel Krügler, questo +trucco unario dovrebbe essere un comportamento specificato, cioè ci si può fidare (vedi discussione nei commenti).

Tuttavia, consiglierei di utilizzare un cast esplicito al tipo di puntatore a funzione se si desidera evitare l'ambiguità: non è necessario chiedere a SO cosa fa e perché funziona;)


3
I puntatori a funzione membro @Fred AFAIK non possono essere convertiti in puntatori a funzione non membro, per non parlare dei valori di funzione. È possibile associare una funzione membro tramite std::binda un std::functionoggetto che può essere chiamato in modo simile a una funzione lvalue.
giorno

2
@DyP: credo che possiamo fare affidamento sul difficile. Supponiamo infatti che un'implementazione si aggiunga operator +()a un tipo di chiusura senza stato. Supponiamo che questo operatore restituisca qualcosa di diverso dal puntatore alla funzione in cui viene convertito il tipo di chiusura. Quindi, questo altererebbe il comportamento osservabile di un programma che viola 5.1.2 / 3. Per favore, fammi sapere se sei d'accordo con questo ragionamento.
Cassio Neri

2
@CassioNeri Sì, questo è il punto in cui non sono sicuro. Sono d'accordo che il comportamento osservabile potrebbe cambiare quando si aggiunge un operator +, ma questo è paragonabile alla situazione con cui non c'è operator +. Ma non è specificato che il tipo di chiusura non avrà un operator +sovraccarico. "Un'implementazione può definire il tipo di chiusura in modo diverso da quanto descritto di seguito a condizione che ciò non alteri il comportamento osservabile del programma se non da [...]" ma IMO l' aggiunta di un operatore non cambia il tipo di chiusura in qualcosa di diverso da quello è "descritto di seguito".
giorno

3
@DyP: La situazione in cui non c'è operator +()è esattamente quella descritta dallo standard. Lo standard consente a un'implementazione di fare qualcosa di diverso da quanto specificato. Ad esempio, aggiungendo operator +(). Tuttavia, se questa differenza è osservabile da un programma, allora è illegale. Una volta ho chiesto in comp.lang.c ++. Moderato se un tipo di chiusura poteva aggiungere un typedef per result_typee l'altro typedefsrichiesto per renderli adattabili (ad esempio da std::not1). Mi è stato detto che non poteva perché questo era osservabile. Proverò a trovare il collegamento.
Cassio Neri

6
VS15 ti dà questo divertente errore: test.cpp (543): errore C2593: 'operator +' è ambiguo t \ test.cpp (543): note: potrebbe essere 'built-in C ++ operator + (void (__cdecl *) (void )) 't \ test.cpp (543): note: o' operatore C ++ integrato + (void (__stdcall *) (void)) 't \ test.cpp (543): note: o' operatore C ++ integrato + (void (__fastcall *) (void)) 't \ test.cpp (543): note: o' built-in C ++ operator + (void (__vectorcall *) (void)) 't \ test.cpp (543): note : durante il tentativo di trovare una corrispondenza con l'elenco degli argomenti '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) test.cpp (543): errore C2088:' + ': illegale per la classe
Ed Lambert,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.