5

Hinweis: Ich verstehe, dass viel von dem, was ich hier tue, in C++ 11 einfacher wäre, aber ich kann es nicht in meinem Projekt verwenden.Warum wird meine Vorlagenspezialisierung kompiliert, wenn sie nicht ausgeführt wird?

Ich mache ein Content-Management-System. Die grundlegenden Anforderungen sind:

  1. Man muss in der Lage sein, "Content Holder" -Klassen zu definieren, die eine beliebige Anzahl von Vektoren enthalten, die jeweils unterschiedliche Werte enthalten. Z.B. IntHolder könnte eine vector<int> halten, FloatAndBoolHolder könnte eine vector<float> und eine vector<bool> halten, und so weiter.
  2. Content-Halter-Klassen müssen eine get<>()-Methode haben. Das Template-Argument für get<>() ist ein Typ. Wenn der Inhaltshalter über einen Vektor mit diesem Typ verfügt, muss get<>() einen Wert aus diesem Vektor zurückgeben, andernfalls muss der Aufruf von get<>() einen Compiler-Fehler generieren. Z.B. Wenn ich ein IntHolder Objekt habe, würde das Aufrufen von get<int>() darauf einen int von seinem Vektor zurückgeben, aber das Aufrufen von get<float>() darauf würde einen Compilerfehler erzeugen.

Ich schaffte es, eine Lösung zu finden, die all dies tut. Warning, Vorlagenrekursionstiefengrenze vor:

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

int value = 'A'; 


// helper struct that saves us from partially specialized method overloads 
template < class RequestedType, class ActualType, class TContentHolder > 
struct Getter; 


// holds a vector of type TContent, recursively inherits from holders of other types 
template < class TContent, class TAddContentHolders > 
class ContentHolder : public ContentHolder< typename TAddContentHolders::ContentType, typename TAddContentHolders::AdditionalContentTypes > 
{ 
public: 
    typedef TContent ContentType; 
    typedef TAddContentHolders AdditionalContentTypes; 

private: 
    typedef ContentHolder< typename TAddContentHolders::ContentType, typename TAddContentHolders::AdditionalContentTypes > ParentType; 

public: 
    vector<ContentType> mVector; 

    ContentHolder() 
    { 
     for (int i = 0; i < 5; ++i) 
     { 
      mVector.push_back(ContentType(value++)); 
     } 
    } 

    virtual ~ContentHolder() {} 

    template < class RequestedType > 
    RequestedType get() 
    { 
     return Getter< RequestedType, ContentType, ContentHolder < TContent, TAddContentHolders > >::get(this); 
    } 
}; 

// specialization for ending the recursion 
template < class TContent > 
class ContentHolder< TContent, bool > 
{ 
public: 
    typedef TContent ContentType; 
    typedef bool AdditionalContentTypes; 

    vector<ContentType> mVector; 

    ContentHolder() 
    { 
     for (int i = 0; i < 5; ++i) 
     { 
      mVector.push_back(ContentType(value++)); 
     } 
    } 

    virtual ~ContentHolder() {} 

    template < class RequestedType > 
    RequestedType get() 
    { 
     return Getter< RequestedType, ContentType, ContentHolder< ContentType, bool > >::get(this); 
    } 
}; 


// default getter: forwards call to parent type 
template < class RequestedType, class ActualType, class TContentHolder > 
struct Getter 
{ 
    static RequestedType get(TContentHolder* holder) 
    { 
     cout << "getter 1" << endl; 
     return Getter< RequestedType, typename TContentHolder::ContentType, typename TContentHolder::AdditionalContentTypes >::get(holder); 
    } 
}; 

// specialized getter for when RequestedType matches ActualType: return value from holder 
template < class RequestedType, class TContentHolder > 
struct Getter< RequestedType, RequestedType, TContentHolder > 
{ 
    static RequestedType get(TContentHolder* holder) 
    { 
     cout << "getter 2" << endl; 
     return holder->mVector[0]; 
    } 
}; 

// specialized getter for end of recursion 
template < class RequestedType > 
struct Getter< RequestedType, RequestedType, bool > 
{ 
    static RequestedType get(ContentHolder< RequestedType, bool >* holder) 
    { 
     cout << "getter 3" << endl; 
     return holder->mVector[0]; 
    } 
}; 

Hier ist, wie Sie es verwenden:

// excuse the ugly syntax 
class MyHolder : public ContentHolder< int, ContentHolder< bool, ContentHolder< char, bool > > > 
{ 
}; 

int main() { 
    MyHolder h; 
    cout << h.get<int>() << endl; // prints an int 
    cout << h.get<bool>() << endl; // prints a bool 
    cout << h.get<char>() << endl; // prints a char 
    //cout << h.get<float>() << endl; // compiler error 
    return 0; 
} 

Dies alles schön und gut ist, und erfüllt alle oben genannten Anforderungen. Allerdings ist der Compilerfehler für die get<float>() wirklich hässlich. Also versuchte ich eine andere Spezialisierung für Getter einzuführen, die für den Fall erklärt, wenn wir das Ende der Klassenhierarchie erreicht und haben noch keine passende gefunden Typ:

// static assert helper 
template <bool b> 
struct StaticAssert {}; 

template <> 
struct StaticAssert<true> 
{ 
    static void test(const string& s) {} 
}; 


template < class RequestedType, class NonMatchingType > 
struct Getter< RequestedType, NonMatchingType, bool > 
{ 
    static RequestedType get(ContentHolder< NonMatchingType, bool >* holder) 
    { 
     cout << "getter 4" << endl; 
     StaticAssert<false>::test("Type not in list"); 
     return 0; 
    } 
}; 

Aber auf diese Weise versagt Kompilierung auf diesem statischen assert auch wenn ich nicht get<float>() anrufen. Noch bizarrer, wenn ich auch die statische Assertion entferne und einfach 0 zurückgebe, kompiliert und läuft der Code, ohne jemals "Getter 4" zu drucken!

Die Frage: Was gibt? Nach meinem Verständnis werden Vorlagen nur dann instanziiert, wenn sie benötigt werden, aber Getter 4 wird niemals ausgeführt. Warum instanziiert der Compiler Getter 4?

anschauliches Beispiel:http://ideone.com/TCSi6G

+0

Wenn Sie eine Funktionsvorlage schreiben, die, wenn instanziiert, einen Fehler erzeugen würde, egal was, kann der Compiler den Fehler direkt erzeugen. Wenn Sie zum Beispiel ein StaticAssert schreiben, das sein zweites Argument ignoriert und dort RequestedType übergibt, lässt der Compiler es passieren. –

+0

@Marc: Ich bin mir nicht sicher, ob ich es verstehe: Kannst du expandieren? – suszterpatt

+0

Nehmen Sie einfach diesen On-Liner: 'Vorlage void f() {static_assert (false," ");}'. Compiler lehnen es ab, obwohl ich niemals f instanziiere.Die Sache ist, es kann keine gültige Instanziierung dieser Vorlage geben, und das ist illegal. –

Antwort

1

Der Compiler kann Ihre "Getter 4" Member-Funktion kompilieren, da der Code nicht auf der Vorlage Argumenten abhängt. Wenn Sie den Code von den Vorlagenargumenten abhängig machen, kann der Compiler den Code erst kompilieren, wenn Sie ihn mit einem bestimmten Typ instanziieren. Ein einfacher Weg, dies zu erreichen, besteht darin, den Typ in der statischen Assertion zu verwenden.

Verwandte Themen