2012-03-29 7 views
0

Ich habe den folgenden Code:C++: Überlastung nicht wählen erwartete Methode

#include <iostream> 
#include <vector> 
using namespace std; 

struct A{}; 
struct B: public A {}; 

template <typename T> 
void foo(const T& obj) { cerr << "Generic case"<< endl;} 

void foo(const A& a) { 
    cerr << "Specific case" << endl; 
} 

int main() { 
    vector<int> v; 
    foo(v); 
    B b; 
    foo(b); 
    A a; 
    foo(a); 
} 

Ausgang ist

  • Allgemein Fall
  • Allgemein Fall
  • Sonderfall

Warum wird foo(const A& a) nicht ausgewählt? für das Objekt B?

Merkwürdiger, wenn ich die Templat-Methode entfernt und haben nur die folgenden:

#include <iostream> 
#include <vector> 

struct A{}; 
struct B: public A {}; 

//template <typename T> 
//void foo(const T& obj) { cerr << "Generic case"<< endl;} 

void foo(const A& a) { 
    cerr << "Specific case" << endl; 
} 

int main() { 
    B b; 
    foo(b); 
    A a; 
    foo(a); 
} 

Der Code kompiliert und der Ausgang ist:

Specific case 
Specific case 

Warum das Vorhandensein des Templat-Verfahren machen ist so ein Unterschied?

Edit: Wie kann ich den Compiler zwingen, die freie Methode für Klassen zu wählen, die von A in Anwesenheit der Vorlagenmethode abgeleitet sind?

Antwort

11

Für den Aufruf von foo(const B&), der die Template-Instanziierung ergibt, ist keine Konvertierung erforderlich, daher ist es die bessere Übereinstimmung.

Wenn ein Funktionsaufruf vom Compiler gesehen wird, muss jede Basisfunktionsvorlage instanziiert werden und ist in der Überladungsmenge enthalten, die mit jeder normalen Funktion verbunden ist. Danach wird die Überladungsauflösung durchgeführt. Es gibt auch SFINAE, wodurch eine Instanziierung einer Funktionsvorlage zu einem Fehler führen kann (eine solche Funktion würde der Überladungsmenge nicht hinzugefügt). Natürlich sind die Dinge nicht wirklich so einfach, aber es sollte das allgemeine Bild geben.

In Bezug auf Ihre Bearbeitung: Es gibt nur eine Methode, um anzurufen. Was könnte es sonst noch geben?

+0

ja - nur die eine Methode vereinfacht die Dinge. Ich vermute, dass ich erwartet habe, dass der const B & call auch in diesem Fall fehlschlägt. – ATemp

+0

'B' ist ein direktes Kind von' A'. Es kann an ein 'A &' oder 'const A &' gebunden werden. – pmr

+0

Und jetzt merke ich, ich habe 31 Minuten damit verbracht, meine Antwort zu verfassen ... SO isst meine Zeit: x –

1

@ PMR answer erklärt, warum die Vorlagenfunktion in Ihrem Beispiel bevorzugt wird. Um zu erzwingen, dass der Compiler stattdessen Ihre Überladung auswählt, können Sie SFINAE verwenden, um die Vorlagenfunktion aus der Überladungsgruppe zu löschen. Ändern Sie den Templat-foo zu

template <typename T> 
typename std::enable_if<!std::is_base_of<A, T>::value>::type 
    foo(const T& obj) { cerr << "Generic case"<< endl;} 

Wenn nun TA oder eine Klasse von A der Templat-Funktion des Rückgabetyp abgeleitet ist, ist ungültig und es wird von der Überladungsauflösung ausgeschlossen. enable_if ist in der type_traits Kopfzeile vorhanden.

+0

Muss ich nicht auch etwas von foo() zurückgeben? Was, wenn ich die Methodensignaturen gleich behalten wollte. Ist es möglich, den Code innerhalb von foo() zu ändern und enable_if zu verwenden, um eine Instanziierung aufgrund des Methodenhauptteils von foo() zu verhindern? – ATemp

+0

@ATemp 'enable_if' hat 2 Vorlagenargumente, von denen der zweite der Rückgabetyp ist. Es ist standardmäßig "leer", weshalb ich es im Beispiel weggelassen habe. Wenn Sie einen anderen Rückgabetyp haben, fügen Sie einfach das zweite Template-Argument hinzu. Sie können nicht verhindern, dass die Vorlagenfunktion während der Überladungsauflösung aufgrund des Codes in ihrem Körper ausgewählt wird. – Praetorian

4

Ja, es ist ein wenig überraschend, aber Vererbung und Vorlage mischen sich nicht so gut, wenn es um Überladungsauflösung geht.

Die Sache ist, wenn der Compiler ausgewählt wird, welche Überladung ausgewählt werden soll, wählt der Compiler diejenige aus, die die wenigsten Konvertierungen benötigt (eingebaut in eingebaute, abgeleitete zu base, Aufrufe zu nicht expliziten Konstruktoren oder Konvertierungsoperatoren) , etc...). Der Ranking-Algorithmus ist eigentlich ziemlich komplex (nicht alle Konvertierungen werden gleich behandelt ...).

Sobald die Überladungen geordnet sind, wird die Vorlage verworfen, wenn die beiden obersten gleichrangig sind und einer eine Vorlage ist.Wenn die Vorlage jedoch einen höheren Rang als die Nichtvorlage aufweist (normalerweise weniger Conversions), wird die Vorlage ausgewählt.

In Ihrem Fall:

  • für std::vector<int> nur eine Überlastung Streichhölzer, so wird es ausgewählt.
  • für A zwei Überladungen übereinstimmen, sie gleichen gleichermaßen, die Vorlage wird verworfen.
  • für B zwei Überladungen übereinstimmen, der Template-Rang höher (keine Umwandlung von abgeleiteten in Basis erforderlich), wird ausgewählt.

Es gibt zwei Workarounds, ist die einfachste zu „reparieren“ die Aufrufort:

A const& ba = b; 
foo(ba); 

Die andere ist die Vorlage selbst zu beheben, aber das ist kniffliger ...

Sie, dass für die Klassen codieren von A abgeleitet ist dies nicht die Überlastung Sie wünschen:

template <typename T> 
typename std::enable_if<not std::is_base_of<A, T>::value>::type 
foo(T const& t) { 
    std::cerr << "Generic case\n"; 
} 

Doch diese flexib nicht so ist le ...

Eine andere Lösung besteht darin, einen Haken zu definieren. Zuerst müssen wir einige metaprogramming Dienstprogramm:

// Utility 
template <typename T, typename Result = void> 
struct enable: std::enable_if< std::is_same<T, std::true_type>::value > {}; 

template <typename T, typename Result = void> 
struct disable: std::enable_if< not std::is_same<T, std::true_type>::value > {}; 

Und dann definieren wir unsere Haken und Funktion:

std::false_type has_specific_foo(...); 

template <typename T> 
auto foo(T const& t) -> typename disable<decltype(has_specific_foo(t))>::type { 
    std::cerr << "Generic case\n"; 
} 

Und dann für jede Basisklasse wollen wir eine spezifische foo:

std::true_type has_specific_foo(A const&); 

In Aktion bei ideone.

Es ist auch in C++ 03 möglich, aber etwas mühsamer. Die Idee ist dieselbe, ein Ellipsenargument ... hat den schlechtesten Rang, also können wir die Überladungsauswahl für eine andere Funktion verwenden, um die Wahl des primären zu steuern.