2010-04-13 6 views
16

Ein Mitarbeiter hat mir kürzlich einen Code gezeigt, den er online gefunden hat. Es scheint zu ermöglichen, dass die Kompilierzeit bestimmt, ob ein Typ eine "ist a" -Beziehung mit einem anderen Typ hat. Ich finde das total genial, aber ich muss zugeben, dass ich keine Ahnung habe, wie das eigentlich funktioniert. Kann mir das jemand erklären?Kompilierzeit Typbestimmung in C++

template<typename BaseT, typename DerivedT> 
inline bool isRelated(const DerivedT&) 
{ 
    DerivedT derived(); 
    char test(const BaseT&); // sizeof(test()) == sizeof(char) 
    char (&test(...))[2]; // sizeof(test()) == sizeof(char[2]) 
    struct conversion 
    { 
     enum { exists = (sizeof(test(derived())) == sizeof(char)) }; 
    }; 
    return conversion::exists; 
} 

Sobald diese Funktion definiert ist, können Sie es wie folgt verwenden:

#include <iostream> 

class base {}; 
class derived : public base {}; 
class unrelated {}; 

int main() 
{ 
    base b; 
    derived d; 
    unrelated u; 

    if(isRelated<base>(b)) 
     std::cout << "b is related to base" << std::endl; 

    if(isRelated<base>(d)) 
     std::cout << "d is related to base" << std::endl; 

    if(!isRelated<base>(u)) 
     std::cout << "u is not related to base" << std::endl; 
} 
+0

Das ist ziemlich verdammt cool Voodoo. – zneak

+0

+1 Awesome Trick. – SLaks

+7

Wenn Sie an diesen Dingen interessiert sind, erhalten Sie eine Kopie von Alexandrescus * "Modern C++ Design" *. –

Antwort

11

Es deklariert zwei überladene Funktionen mit dem Namen test, eine unter Base und eine unter (...), und verschiedene Arten zurückgeben.

Es ruft dann die Funktion mit einem Derived auf und überprüft die Größe seines Rückgabetyps, um zu sehen, welche Überladung aufgerufen wird. (Er ruft tatsächlich die Funktion mit dem Rückgabewert einer Funktion, die Derived zurückkehrt, zu vermeiden Speicher verwenden)

Da enum s Kompilierung-Konstanten sind, all dies innerhalb des Typs System zur Compile-Zeit getan. Da die Funktionen nicht zur Laufzeit aufgerufen werden, spielt es keine Rolle, dass sie keine Körper haben.

+0

'abgeleitete()' ist nicht zum Speichern von Speicher - die 'sizeof()' Ausdruck wird sowieso zur Kompilierzeit ausgewertet - es ist eine Möglichkeit, nicht 'Abgeleitet zu sein, um standardmäßig konstruierbar (dh zu sein Vermeiden Sie die Verwendung von 'test (Derived())'). –

+0

@gf: Ich meinte, im Gegensatz zu einem Zeiger auf einen 'Dervied 'zu deklarieren. – SLaks

6

Ich bin kein C++ Experte, aber es scheint mir, wie der Punkt, den Compiler zu erhalten, ist zwischen entscheiden die beiden Überladungen von test(). Wenn Derived von Base abgeleitet wird, wird die erste verwendet, die char zurückgibt, andernfalls wird die zweite verwendet - die char[2] zurückgibt. Der Operator sizeof() bestimmt dann, welcher von diesen passiert ist und setzt den Wert von conversion::exists entsprechend.

+0

true..its nur basierend auf der Überladungsauflösung. – mukeshkumar

+0

nette Erklärung :) –

5

Es ist ziemlich cool, aber es funktioniert nicht wirklich, weil benutzerdefinierte Konvertierung vor Ellipsenübereinstimmung bevorzugt wird, und Const-Referenz das temporäre Ergebnis einer benutzerdefinierten Konvertierung binden kann. So würde char*p; is_related<bool>(p);true (getestet in VS2010) zurückgeben.

Wenn Sie wirklich nach einer Vererbungsbeziehung testen möchten, können Sie einen ähnlichen Ansatz verwenden, aber Zeiger anstelle von Referenzen verwenden.

2

Übrigens können Sie __is_base_of von "type_traits" in std::tr1 eingeführt (MSCV 2008 Compiler hat intrinsics Unterstützung dafür).

5

Gibt es einen Grund, warum Sie nicht so etwas wie dies stattdessen verwenden würde:

template<typename BaseT, typename DerivedT> 
struct IsRelated 
{ 
    static DerivedT derived(); 
    static char test(const BaseT&); // sizeof(test()) == sizeof(char) 
    static char (&test(...))[2]; // sizeof(test()) == sizeof(char[2]) 

    enum { exists = (sizeof(test(derived())) == sizeof(char)) }; 
} 

?

z.B .:

IsRelated<Base, Derived>::exists 

diese Weise erhalten Sie Zugriff auf die Informationen zur Compile-Zeit haben.

1

Der ursprüngliche Code wird ein Objekt von Derived konstruieren, es kann zu unerwarteten Ergebnissen führen. Unterhalb der Arbeit kann eine Alternative sein:

template<typename BaseT, typename CheckT> 
inline bool isDerived(const CheckT &t){ 
    char test(const BaseT *t); 
    char (&test(...))[2]; 
    return (sizeof(test(&t)) == sizeof(char)); 
}