2015-05-22 9 views
29

Ich versuche zu verstehen, warum std::function nicht in der Lage ist, zwischen überladenen Funktionen zu unterscheiden.std :: function kann überladene Funktionen nicht unterscheiden

#include <functional> 

void add(int,int){} 

class A {}; 

void add (A, A){} 

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

Im Code oben gezeigt, kann function<void(int, int)> nur eine dieser Funktionen entsprechen und doch scheitert es. Warum ist das so? Ich weiß, dass ich das umgehen kann, indem ich einen Lambda oder einen Funktionszeiger auf die tatsächliche Funktion verwende und den Funktionszeiger dann in Funktion speichere. Aber warum scheitert das? Ist der Kontext nicht klar, für welche Funktion ich ausgewählt werden möchte? Bitte helfen Sie mir zu verstehen, warum dies fehlschlägt, da ich nicht verstehen kann, warum in diesem Fall das Vorlagen-Matching fehlschlägt.

Die Compiler-Fehler, die ich für diese auf Klirren erhalten sind wie folgt:

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 - Neben MSalters' Antwort, ich auf diesem Forum einige der Suche tat und den genauen Grund gefunden, warum dies nicht gelingt. Ich habe die Antwort von Nawaz Antwort in dieser post bekommen.

Ich habe Kopie hier aus seiner Antwort eingefügt:

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! 

warum So std::function<int(const std::string&)> nicht die Art und Weise nicht funktionieren funtype fun = test oben funktioniert?

Nun, die Antwort ist, weil std::function kann mit jedem Objekt initialisiert werden, als Konstruktor templatized, die von der Vorlage Argumente unabhängig Sie std::function weitergegeben.

+3

Welcher Compiler schlägt fehl? – EdChum

+0

gcc 4.8, clang und Visual Studio 2013.Ich kann die Compilerfehler veröffentlichen, wenn das erforderlich ist, obwohl sie nicht sehr freundlich sind. – Madhusudhan

+0

Compiler-Fehler sind in der Regel nicht freundlich, aber es lohnt sich, es zu veröffentlichen, es ist seltsam, was hier mehrdeutig wird, fast so, als ob der Typ der Klasse 'A' irgendwie als 'int' betrachtet wird, tritt der Fehler immer noch auf, wenn Sie erklären 'add' als' double's? – EdChum

Antwort

21

Es ist offensichtlich, uns schreiben würde es ermöglichen, die Sie funktionieren beabsichtigen, gewählt werden, aber der Compiler hat die Regeln von C++ verwenden, nicht klug Sprünge von Logik folgen (oder gar nicht so schlau wie in einfachen Fällen wie diesem!)

)

Der relevante Konstruktor std::function ist:

template<class F> function(F f); 

die eine Vorlage ist, die jede Art übernimmt.

Die C++ 14-Standard nicht die Vorlage constrain (da LWG DR 2132), so dass es:

darf nicht in die Überladungsauflösung teilnehmen, es sei denn f aufrufbare ist (20.9.12.2) für Argumenttypen ArgTypes... und Typ zurückkehren R.

was bedeutet, dass der Compiler wird nur der Konstruktor erlauben aufgerufen werden, wenn Functor mit dem Aufruf Unterzeichnung des std::function kompatibel ist (die void(int, int) in Ihrem Beispiel ist). In der Theorie sollte das bedeuten, dass void add(A, A) kein praktikables Argument ist und so "offensichtlich" Sie beabsichtigten, void add(int, int) zu verwenden.

jedoch kann der Compiler nicht testen die „f für Argumenttypen aufrufbare ist ...“ Zwang, bis er die Art von f weiß, was bedeutet, es bereits zu haben braucht vereindeutigt zwischen void add(int, int) und void add(A, A)vor Es kann die Einschränkung anwenden, die es erlauben würde, eine dieser Funktionen abzulehnen!

So gibt es ein Huhn und Ei-Situation, die leider bedeutet, dass Sie den Compiler aus durch die Angabe genau, welche Überlastung von add Sie verwenden möchten, und dann der Compiler helfen müssen, können die Einschränkung gelten und (eher redundant) Entscheiden Sie, dass es ein akzeptables Argument für den Konstruktor ist.

Es ist denkbar, dass wir C++ ändern könnten, so dass dies in Fällen wie alle die überladenen Funktionen gegen die Einschränkung getestet werden (also nicht, müssen wir wissen, was man vor der Prüfung zu testen) und wenn nur ein ist machbar, dann benutze das, aber so funktioniert C++ nicht.

+0

Vielen Dank Jonathan Wakely. Ich hatte die Frage bearbeitet, bevor Sie diese Antwort gepostet haben, um anzuzeigen, dass ich den templated Konstruktor von einer anderen verwandten Frage herausgefunden habe. Aber deine Antwort kommt mir am nächsten. Vielen Dank für die Aufklärung. – Madhusudhan

+1

Der Standard erfordert Versuchsabzüge, wenn das Argument eine Überladungsmenge ist, aber nur, wenn der Parameter vom Zeiger zur Funktion, vom Zeiger zur Elementfunktion oder zum Funktionstyp ist (siehe [temp.deduct.call]/p6) Level cv-Qualifier und Referenz. –

+0

@ T.C. Es ist eine sehr interessante Idee, aber nur Abzugschaden ist erlaubt, wenn man den Überlast-, nicht den Substitutionsfehler auswählt. Es gibt also keine Möglichkeit, diesen Mechanismus in einem solchen generischen Kontext wie 'std :: function' anzuwenden. – Potatoswatter

4

Versuchen:

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

Adressen void add(A, A) und void add(int, int) obvoiusly, differiert. Wenn Sie auf die Funktion mit Namen zeigen, ist es für den Compiler ziemlich unmöglich zu wissen, welche Funktionsadresse Sie brauchen. void(int, int) hier ist kein Hinweis.

+1

Hallo, ich weiß, wie man das umgeht. Aber meine Frage ist: "Warum gibt es überhaupt eine Zweideutigkeit?" Sind Funktionsparameter nicht wesentlich Teil der Signatur, die Ihnen helfen soll, die richtige Funktion zu wählen? – Madhusudhan

16

Während es offensichtlich ist, was Sie wollen, ist das Problem, dass std::function kann nicht beeinflussen, Überlastung Auflösung von &add. Wenn Sie einen Raw-Funktionszeiger initialisieren (void (*func)(int,int) = &add), funktioniert es. Das liegt daran, dass die Initialisierung des Funktionszeigers ein Kontext ist, in dem die Überladungsauflösung erfolgt. Der Zieltyp ist genau bekannt. Aber std::function dauert fast jedes Argument, das abrufbar ist. Diese Flexibilität beim Akzeptieren von Argumenten bedeutet, dass Sie keine Überladungsauflösung unter &add durchführen können. Mehrere Überladungen von add könnten geeignet sein.

Eine explizite Umwandlung funktioniert, das heißt static_cast<void(*)(int, int)> (&add).

Dies kann in einer template<typename F> std::function<F> make_function(F*) gewickelt werden, die Sie auto func = make_function<int(int,int)> (&add)

+0

Danke MSalters. Wird sich das in Zukunft ändern? Die Überladungsauflösung scheint hier sinnvoll zu sein, denn während ich das 'function'-Objekt erstelle, spezifiziere ich genau die Art von aufrufbaren Objekten, die ich haben möchte. Ich verstehe, dass Funktion mit Klassen mit überladenen Operator() oder Lambdas oder regulären Funktionen umgehen kann. Aber sollte nicht überladen Auflösung (obwohl streng genommen gibt es keine Überladung Auflösung hier als Lambdas und Klassen im Wesentlichen nicht überladen eine reguläre Funktion) oder etwas ähnliches hier gelten – Madhusudhan

+2

@ user3493289: Ich würde nicht erwarten, dass es in der Zukunft ändern. Beachten Sie, dass, wenn 'int' implizit in' A' und zurück konvertiert werden kann, 'std :: function ' auch 'A add (A, A)' enthalten kann. Es ist diese Flexibilität, die das Überladen nicht nur technisch, sondern auch logisch unmöglich macht. – MSalters

+1

Huh. Ich habe so viele Funktion-oid-Klassen geschrieben, dass ich anscheinend vergessen habe, dass 'std :: function ' eine 'void (*) (int, int) 'Überladung fehlt. Wäre eine solche Überladung nicht harmlos und würde das obige funktionieren? – Yakk

-2

Soweit ich sehen kann, ist es ein Visual Studio Problem.

C++ 11-Standard (20.8.11)

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

aber Visual Studio nicht über diese Spezialisierung

Klirren ++ und g ++ völlig in Ordnung ist mit Überlastung std :: Funktionen

vor Antworten erklären, warum VS nicht funktioniert, aber sie haben nicht erwähnt, dass es VS Bug

+0

Vorherige Antworten geben Erklärungen, die darauf hinweisen, dass Ihre Antwort falsch ist. Haben Sie ein Beispiel, das zeigt, wie Ihre Antwort den bereits geposteten Antworten etwas hinzufügt? – Prune

+0

Vorherige Antwort erklären schlecht VS-Verhalten, das ist alles. Sie führen in die Irre. So einfach ist das. Bitte lesen Sie den Standard –

1

ist Eine andere Möglichkeit, damit umzugehen ist mit einem generischen Lambda in C++ 14:

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

Das wird eine Lambda-Funktion erstellen, die Dinge ohne Zweideutigkeit lösen wird. Ich habe keine Argumente weitergeleitet,