2013-09-24 9 views
13

Ich versuche, einige Template-Funktion, um eine Überlastung bestimmte Aktion auszuführen, wenn ich es nennen eine bestimmte Klasse mit MyClass oder jede abgeleitete Klasse MyClassDer. Hier ist der Code:Überlastung Funktion und Vererbung

#include <iostream> 

struct MyClass { 
    virtual void debug() const { 
     std::cerr << "MyClass" << std::endl; 
    }; 
}; 

struct MyClassDer : public MyClass { 
    virtual void debug() const { 
     std::cerr << "MyClassDer" << std::endl; 
    }; 
}; 

template <typename T> void func (const T& t) { 
    std::cerr << "func template" << std::endl; 
} 

void func (const MyClass& myClass) { 
    std::cerr << "func overloaded" << std::endl; 
    myClass.debug(); 
} 


int main(int argc, char **argv) { 
    func (1); 
    MyClass myClass; 
    func (myClass); 
    MyClassDer myClassDer; 
    func (myClassDer); 
} 

Die Ausgabe lautet:

func template 
func overloaded 
MyClass 
func template 

func (myClassDer) ruft die Template-Funktion statt void func (const MyClass& myClass). Was kann ich tun, um das erwartete Verhalten zu erhalten?

Dank

Antwort

3

können Sie SFINAE verwenden:

#include <type_traits> 

template <typename T> 
void func (const T& t, typename std::enable_if<!std::is_base_of<MyClass, T>::value>::type * = nullptr) { 
    std::cout << "func template" << std::endl; 
} 

template < 
    typename T 
    , typename = typename std::enable_if<std::is_base_of<MyClass, T>::value>::type 
> 
void func (const T& t) { 
    std::cout << "func overloaded" << std::endl; 
    t.debug(); 
} 

Wenn Sie C++ 11 nicht haben, Boost bietet die gleiche Funktionalität.

Live example

EDIT

Dies ohne C++ 11 (mit Boost) funktionieren sollte:

#include "boost/type_traits.hpp" 

template <typename T> 
void func (const T& t, typename boost::enable_if<!boost::is_base_of<MyClass, T>::value>::type * = 0) { 
    std::cout << "func template" << std::endl; 
} 

template <typename T> 
void func (const T& t, typename boost::enable_if<boost::is_base_of<MyClass, T>::value>::type * = 0) { 
    std::cout << "func overloaded" << std::endl; 
    t.debug(); 
} 
+0

Dies scheint die richtige Lösung zu sein. Ich werde sehen, wie ich das mit Boost umsetzen kann. – user2811040

+0

@ user2811040 'boost :: enable_if' und' boost :: is_base_of'. Das ist es. – Angew

+0

@ user2811040 Und ohne C++ 11, verschieben Sie den zweiten Template-Parameter auch auf den Zeiger-Trick. – Angew

0
MyClass *myClassDer = new MyClassDer; 
func(*myClassDer); 
delete myClassDer; 
+1

Jeder Grund für die dynamische Zuweisung einzuführen? – Angew

+0

Das Überladen von Funktionen wird zur Kompilierzeit gelöst, daher brauchen Sie 'MyClass', um die richtige Funktion zu treffen. Damit Laufzeit-Polymorphismus funktioniert, benötigen Sie jedoch ein Objekt von 'MyClassDer'. Daher "neu". – HAL

+0

mit dynamischer Zuordnung ändert nichts. Polymorphismus funktioniert gut mit Referenzen ... – user2811040

0

Sie müssen Polymorphismus verwenden, um Ihre Template-Funktion aufzurufen. Sie müssen einen Verweis auf Ihre Basisklasse:

int main(int argc, char **argv) { 
    func (1); 
    MyClass myClass; 
    func (myClass); 
    MyClassDer myClassDer; 
    MyClass* mc = &myClassDer; 
    func (*mc); 
} 

More polymorphism examples and details here

+1

Sie könnten auch nur eine Referenz erstellen und entfernen Sie ein paar '* –

4

Denn warum Ihr Code nicht funktioniert: siehe @ David ausgezeichnete Erklärung. Um es zu umgehen, können Sie SFINAE verwenden ("Substitutions Scheitern ist keine Errro) durch einen versteckten Template-Parameter hinzugefügt Requires (der Name für Dokumentationszwecke ist nur)

template < 
    typename T, typename Requires = typename 
    std::enable_if<!std::is_base_of<MyClass, T>::value, void>::type 
> 
void func (const T& t) { 
    std::cerr << "func template" << std::endl; 
} 

Dies wird deaktivieren Sie diese Vorlage für die Überladungsauflösung Immer wenn T gleich oder abgeleitet von MyClass ist, wird stattdessen die reguläre Funktion ausgewählt (für die Abgeleitete-zu-Base-Konvertierungen durchgeführt werden, im Gegensatz zur Template-Argumentableitung, die nur exakte Übereinstimmungen berücksichtigt.) Sie können natürlich damit spielen Dies und fügen Sie mehrere Überlastungen mit nicht überlappenden Bedingungen innerhalb der std::enable_if, um eine feinkörnige Auswahl von Funktionsüberladungen, die in Betracht gezogen werden, zu haben. Aber seien Sie vorsichtig, SFINAE ist subtil!

Live Example.

Hinweis: Ich schrieb meine SFINAE mit C++ 11 Syntax, mit einem Standard-Vorlagenparameter für Funktionsvorlagen. In C++ 98 müssen Sie entweder einen regulären Standardparameter hinzufügen oder den Rückgabetyp ändern.

+0

Ausgezeichnete !!!!!! – shofee

0

Sein, weil Ihre überladenen Funktion Unterschrift ist,

void func (const MyClass& myClass) 
{ 
    std::cerr << "func overloaded" << std::endl; 
    myClass.debug(); 
} 

heißt es MyClass als Parameter will, und Sie nennen es MyClassDer verwenden. Zur Kompilierzeit löst es also die andere überladene Funktion auf und verbindet sich damit. Da die andere Funktion templated ist, gibt es kein Problem für den Compiler, damit zu verlinken.

Also, wenn Sie ein MyClassDer Objekt übergeben möchten, könnten Sie es immer noch mit Polymorphie tun.

MyClass *myClassDer = new MyClassDer; 
func(*myClassDer); 
+4

Keine Notwendigkeit, "neue" nichts, Sie können den Zeiger auf eine lokale Variable (oder besser sogar, eine Referenz) erstellen –

1

Polymorphismus tritt in Laufzeit, aber eine überladene Funktion Auswahl erfolgt in Kompilierzeit.

in der Kompilierung So ist die beste Überlastung MyClassDer zu akzeptieren ist

func<MyClassDer> (const MyClassDer& t) 

statt

func<MyClass> (const MyClass& t) 

dann Compiler die erste wählt.


Eine Möglichkeit, das Problem zu lösen ist:

func(static_cast<MyClass&>(myClassDer)); 
11

Dies ist nur, wie die Überladungsauflösung funktioniert. Wenn die Suche abgeschlossen ist, werden sowohl die Vorlage als auch die Funktion gefunden. Die Vorlagentypen werden dann abgeleitet und die Überladungsauflösung beginnt. Im Fall eines Arguments des Typs MyClass die beiden candiates sind:

void func<MyClass>(MyClass const&); 
void func(MyClass const&); 

die gleichermaßen gute Spiele für die Argumente sind, aber der zweite ist ein Nicht-Template sind bevorzugt. Im Fall von MyClassDer:

void func<MyClassDer>(MyClassDer const&); 
void func(MyClass const&); 

In diesem Fall ist die erste ein besserer Kandidat ist als die zweite, als die zweite erfordert eine abgeleitetes-Basis-Umwandlung und das aufgenommen wird.

Es gibt verschiedene Ansätze zum direkten Versand, um Ihren Code zu treffen. Die einfachste ist nur die Art des Arguments Nötigung sein MyClass und Rückfall so auf den ursprünglichen Fall: einfache

func(static_cast<MyClass&>(myClassDer)); 

Während dies muss überall getan werden, und wenn Sie nur an einer Stelle vergessen, die falsche Sache wird heißen. Der Rest der Lösungen sind Komplex und Sie könnten prüfen, ob es nicht besser wäre, nur verschiedene Funktionsnamen bereitzustellen.

Eine der Optionen ist SFINAE mit der Vorlage zu deaktivieren, wenn der Typ von MyClass abgeleitet:

template <typename T> 
typename std::enable_if<!std::is_base_of<MyClass,MyClassDer>::value>::type 
func(T const & t) { ... } 

In diesem Fall wird nach Nachschlag wird der Compiler-Typ Abzug durchführen, und es wird ableiten T zu be , wird es dann den Rückgabetyp der Funktion auswerten (SFINAE könnte auch auf ein anderes Vorlage- oder Funktionsargument angewendet werden). Die is_base_of ergibt false und die enable_if wird keinen verschachtelten Typ haben. Die Funktionsdeklaration wird schlecht gebildet und der Compiler wird sie fallen lassen, wobei die Auflösung mit einem einzelnen Kandidaten, der Nicht-Template-Überladung, gesetzt bleibt.

Eine weitere Option wäre die Bereitstellung einer einzelnen Vorlagenschnittstelle und die interne Verteilung an eine Vorlage oder die Überladung (mit einem anderen Namen) mittels Tag-Versand. Die Idee ist ähnlich, Sie bewerten das Merkmal innerhalb der Vorlage und rufen eine Funktion mit einem Typ auf, der aus dieser Auswertung generiert wurde.

template <typename T> 
void func_impl(T const&, std::false_type) {...} 
void func_impl(MyClass const&, std::true_type) {...} 

template <typename T> 
void func(T const &x) { 
    func_impl(x,std::is_base_of<MyClass,MyClassDer>::type()); 
} 

Es gibt andere Alternativen, aber das sind zwei gewöhnlichsten und der Rest auf den gleichen Prinzipien basieren hauptsächlich.

Betrachten Sie erneut, ob das Problem die Komplexität der Lösung wert ist. Sofern der Aufruf an func nicht innerhalb des generischen Codes erfolgt, löst eine einfache Änderung des Funktionsnamens das Problem, ohne unnötigerweise Komplexität hinzuzufügen, die Sie oder die anderen Betreuer möglicherweise nicht pflegen können.

+2

Haben Sie diese riesige Antwort nur in ein paar Sekunden eingegeben? +1 – deepmax

+0

@MM .: Mehr wie ein paar Minuten ... Ich bin kein langsamer Typ, aber ich bin nicht der Flash –

+0

@MM. Die Heilige Dreifaltigkeit der Namenssuche, Argumentabzug und Überladungsauflösung erscheint so oft, dass es sich lohnt, diese mehr oder weniger im Arbeitsspeicher zu haben, wenn man Vorlagencode schreibt. Siehe z.B. Stephan T. Lavavejs Vorlesungen über [Core C++] (http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-) – TemplateRex

0

werfen Sie es einfach in den Basistyp:

MyClassDer myClassDer; 
func(static_cast<MyClass&>(myClassDer)); 
+0

static_cast ist keine gute Lösung für mich, da ich nicht wirklich erwarten kann, dass Benutzer meiner Funktion dieses Konstrukt verwenden. Außerdem wird meine Funktion tatsächlich ein Operator sein! – user2811040

+0

@ user2811040 Ah. Und ich nehme an, Sie möchten, dass die Benutzer Ihrer Funktion auch MyClass selbst erweitern können.In diesem Fall scheinen Sie die richtige Antwort akzeptiert zu haben. –

Verwandte Themen