2015-05-22 17 views
29

Sto cercando di capire perché std::function non è in grado di distinguere tra funzioni sovraccariche.std :: function non riesce a distinguere le funzioni sovraccariche

#include <functional> 

void add(int,int){} 

class A {}; 

void add (A, A){} 

int main(){ 
     std::function <void(int, int)> func = add; 
} 

Nel codice mostrato sopra, function<void(int, int)> può competere solo una di queste funzioni, eppure fallisce. Perché è così? So che posso aggirare questo problema usando una lambda o un puntatore a funzione della funzione effettiva e quindi memorizzando il puntatore funzione in funzione. Ma perché questo fallisce? Non è chiaro il contesto su quale funzione voglio essere scelta? Per favore aiutami a capire perché questo non funziona perché non sono in grado di capire perché la corrispondenza dei modelli fallisce in questo caso.

Gli errori del compilatore che ricevo su clang per questo sono i seguenti:

test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to 
     'std::function<void (int, int)>' 
     std::function <void(int, int)> func = add; 
            ^ ~~~ 
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note: 
     candidate constructor not viable: no overload of 'add' matching 
     'std::__1::nullptr_t' for 1st argument 
    _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {} 
          ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note: 
     candidate constructor not viable: no overload of 'add' matching 'const 
     std::__1::function<void (int, int)> &' for 1st argument 
    function(const function&); 
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note: 
     candidate template ignored: couldn't infer template argument '_Fp' 
     function(_Fp, 
    ^
1 error generated. 

EDIT - Oltre alla risposta MSalters', ho fatto qualche ricerca su questo forum e ho trovato il motivo esatto per cui questo non riesce. Ho ricevuto la risposta dalla risposta di Nawaz in questo post.

ho copia incollato dalla sua risposta qui:

int test(const std::string&) { 
     return 0; 
    } 

    int test(const std::string*) { 
     return 0; 
    } 

    typedef int (*funtype)(const std::string&); 

    funtype fun = test; //no cast required now! 
    std::function<int(const std::string&)> func = fun; //no cast! 

Allora perché std::function<int(const std::string&)> non funziona il modo in cui funziona funtype fun = test di cui sopra?

Beh, la risposta è, perché std::function possono essere inizializzati con qualsiasi oggetto, come il suo costruttore è templatized che è indipendente dell'argomento modello è stato passato a std::function.

+3

Quale compilatore non riesce? – EdChum

+0

gcc 4.8, clang e Visual Studio 2013.Posso postare gli errori del compilatore se è necessario anche se non sono molto amichevoli. Gli errori del compilatore – Madhusudhan

+0

di solito non sono amichevoli, ma vale la pena di postarlo, è strano che questo qui diventi ambiguo, quasi che il tipo di classe 'A' sia in qualche modo visto come' int', l'errore si verifica ancora se si dichiara 'aggiungi' come se fosse' double's? – EdChum

risposta

21

E 'ovvio per noi quale funzione si intende essere scelto, ma il compilatore deve seguire le regole del C++ non utilizzare salti intelligenti di logica (o anche non così intelligenti, come in casi semplici come questo!)

Il costruttore dedicata del std::function è:

template<class F> function(F f); 

che è un modello che accetta qualsiasi tipo.

Lo standard C++ 14 fa vincolare il modello (dal LWG DR 2132) in modo che:

non partecipa alla risoluzione di sovraccarico a meno f è invocabile (20.9.12.2) per tipi di argomento ArgTypes... e tipo di ritorno R.

il che significa che il compilatore consentire solo il costruttore da chiamare quando Functor è compatibile con la firma chiamata del std::function (che è void(int, int) nel tuo esempio). In teoria ciò dovrebbe significare che void add(A, A) non è un argomento valido e quindi "ovviamente" intendevi usare void add(int, int).

Tuttavia, il compilatore non può testare il "f è richiamabile per tipi di argomento ..." vincolo finché non conosce il tipo di f, che significa che deve già disambiguare tra void add(int, int) e void add(A, A)prima può applicare il vincolo che gli consentirebbe di rifiutare una di quelle funzioni!

Quindi c'è una situazione dell'uovo e della gallina, il che significa, purtroppo, che è necessario per aiutare il compilatore fuori specificando esattamente quali sovraccarico add che si desidera utilizzare, e poi il compilatore può applicare il vincolo e (piuttosto ridondante) decidere che è un argomento accettabile per il costruttore.

È concepibile che si possa cambiare C++ in modo che in casi come questo tutte le funzioni sovraccaricate sono testati contro il vincolo (in modo che non hanno bisogno di sapere quale testare prima del test) e se solo una è fattibile, quindi usa quello, ma non è così che funziona il C++.

+0

Grazie mille Jonathan Wakely. Avevo modificato la domanda prima di pubblicare questa risposta per indicare che ho scoperto il costruttore templatizzato da un'altra domanda correlata. Ma la tua risposta è la più vicina a ciò che volevo. Grazie mille per il chiarimento. – Madhusudhan

+1

Lo standard richiede deduzioni di prova quando l'argomento è un set di sovraccarico, ma solo se il parametro è di puntatore a funzione, puntatore alla funzione membro o tipo di funzione (si veda [temp.deduct.call]/p6) dopo aver rimosso top- livello cv-qualificatori e referenze. –

+0

@ T.C. È un'idea molto interessante, ma solo il fallimento della deduzione è permesso nella selezione del sovraccarico, non nel fallimento della sostituzione. Quindi non c'è modo di applicare quel meccanismo in un contesto così generico come 'std :: function'. – Potatoswatter

4

Prova:

std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add); 

indirizzi a void add(A, A) e void add(int, int) differes cosa volessero. Quando si punta alla funzione per nome, è praticamente impossibile per il compilatore sapere quale indirizzo di funzione è necessario. void(int, int) qui non è un suggerimento.

+1

Ciao, so come aggirare questo. Ma la mia domanda è "Perché c'è un'ambiguità in primo luogo"? I parametri di funzione non sono essenzialmente parte della firma che dovrebbe aiutare a scegliere la funzione corretta? – Madhusudhan

16

Mentre è ovvio ciò che si desidera, il problema è che std::function non può influenzare la risoluzione di sovraccarico di &add. Se dovessi inizializzare un puntatore a funzioni non elaborate (void (*func)(int,int) = &add), funziona. Questo perché l'inizializzazione del puntatore funzione è un contesto in cui viene eseguita la risoluzione di sovraccarico. Il tipo di target è esattamente conosciuto. Ma std::function prenderà quasi tutti gli argomenti che possono essere richiamati. Questa flessibilità nell'accettare argomenti significa che non è possibile eseguire la risoluzione di sovraccarico su &add. Potrebbero essere adatti sovraccarichi multipli di add.

Un cast esplicito funzionerà, ovvero static_cast<void(*)(int, int)> (&add).

questo può essere avvolto in un template<typename F> std::function<F> make_function(F*) che permetterebbe di scrivere auto func = make_function<int(int,int)> (&add)

+0

Grazie MSalters. È qualcosa che cambierà in futuro? La risoluzione del sovraccarico sembra essere una cosa valida da fare qui perché mentre creo l'oggetto 'function', sto specificando esattamente il tipo di oggetti callable che voglio. Comprendo che la funzione può gestire classi con overload() o lambdas o funzioni regolari. Ma non dovrebbe sovraccaricare la risoluzione (anche se in senso stretto non esiste una risoluzione di sovraccarico qui come lambdas e le classi non sovraccaricano essenzialmente una funzione regolare) o qualcosa di simile essere applicabile qui – Madhusudhan

+2

@ user3493289: non mi aspetto che cambi in futuro. Si noti che se 'int' è implicitamente convertibile in' A' e ritorno, quindi 'std :: function ' può contenere anche 'A add (A, A)'. È quella flessibilità che rende l'overloading non solo tecnicamente, ma anche logicamente impossibile. – MSalters

+1

Huh. Ho scritto così tante classi di funzioni-oid che apparentemente ho dimenticato che 'std :: function ' manca un sovraccarico 'void (*) (int, int)'. Un tale sovraccarico non sarebbe innocuo e farà in modo che funzioni sopra? – Yakk

-2

Per quanto posso vedere, si tratta di un problema di Visual Studio.

C++ 11 normale (20.8.11)

std::function synopsis 
template<class R, class... ArgTypes> class function<R(ArgTypes...)>; 

ma VisualStudio non ha che la specializzazione

clang ++ e g ++ sono perfettamente bene con sovraccarico std :: funzioni

prima le risposte spiegano perché VS non funziona, ma non hanno menzionato che è il bug di VS

+0

Le risposte precedenti forniscono spiegazioni che indicano che la risposta è errata. Hai un esempio che mostra come la tua risposta aggiunge qualcosa alle risposte già pubblicate? – Prune

+0

Prima risposta spiegare cattivo comportamento VS, questo è tutto. Ingannano. È così semplice, per favore, leggi lo standard –

1

Un altro modo per affrontare questo è con un lambda generico in C++ 14:

int main() { 
    std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...); 
} 

Ciò creerà una funzione lambda che risolverà le cose senza ambiguità. Non inoltro argomenti,