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:
- 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 einevector<int>
halten,FloatAndBoolHolder
könnte einevector<float>
und einevector<bool>
halten, und so weiter. - Content-Halter-Klassen müssen eine
get<>()
-Methode haben. Das Template-Argument fürget<>()
ist ein Typ. Wenn der Inhaltshalter über einen Vektor mit diesem Typ verfügt, mussget<>()
einen Wert aus diesem Vektor zurückgeben, andernfalls muss der Aufruf vonget<>()
einen Compiler-Fehler generieren. Z.B. Wenn ich einIntHolder
Objekt habe, würde das Aufrufen vonget<int>()
darauf einenint
von seinem Vektor zurückgeben, aber das Aufrufen vonget<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
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. –
@Marc: Ich bin mir nicht sicher, ob ich es verstehe: Kannst du expandieren? – suszterpatt
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. –