Template Metaprogramming
Einfach alle Objekte mit anderen Grenzen für eine Zuweisung abweisen ist nicht ganz das richtige. Eigentlich möchten wir abhängig von den Grenzen des anderen Objektes lieber eine Prüfung durchführen oder eben auch nicht. Template Metaprogramming macht genau das möglich.
Video
Code
#include <iostream>
#include <stdexcept>
template <bool> inline void check(int value, int min, int max);
template <> inline void check<true>(int value, int min, int max)
{
std::cerr << "Check!\n";
if (value < min || value > max) {
throw std::out_of_range("Ungueltiger Wert");
}
}
template <> inline void check<false>(int, int, int)
{
}
template <int min, int max> class RangeInt
{
int mValue;
public:
RangeInt(int value)
{
check<true>(value, min, max);
mValue = value;
}
template <int otherMin, int otherMax>
RangeInt(RangeInt<otherMin, otherMax> const &other)
{
check< (otherMin < min || otherMax > max) >(other.getValue(), min, max);
mValue = other.getValue();
}
template <int otherMin, int otherMax>
RangeInt &operator=(RangeInt<otherMin, otherMax> const &other)
{
check< (otherMin < min || otherMax > max) >(other.getValue(), min, max);
mValue = other.getValue();
}
operator int() const
{
return mValue;
}
int getValue() const
{
return mValue;
}
};
int main()
{
RangeInt<5, 20> r1(10);
RangeInt<15, 30> r2(25);
RangeInt<6, 10> r3(8);
RangeInt<5, 20> r4(9);
std::cerr << r1 << std::endl;
r1 = r3;
std::cerr << r1 << std::endl;
r1 = r4;
std::cerr << r1 << std::endl;
r1 = r2;
std::cerr << r1 << std::endl;
}
Erklärung
Unser RangeInt-Template vom letzten Mal ist etwas über's Ziel hinaus
geschossen: wir können nun nur noch Objekte gleicher Typen aufeinander zuweisen.
Eigentlich könnten wir die Regel etwas lockerer gestalten: Sind die Grenzen der
Quelle enger oder gleich den Grenzen des Ziels, dann können wir uns die
Bereichsprüfung sparen. Sonst müssen wir sie durchführen. Das Problem ist hier:
wir müssen diese Prüfung bei der Übersetzung durchführen.
Die Technik, die uns das erlaubt, nennt sich Template Metaprogramming. So
richtig Absicht war das nicht, dass man die in C++ eingebaut hat. Sie folgt
vielmehr aus den Regeln, nach denen Templates zur Instanziierung ausgewählt
werden und wie Compile-Time-Konstanten funktionieren. In unserem Fall steckt die
Hauptarbeit im Funktionstemplate check und seinen beiden Spezialisierungen in
Zeile 6 und 14. Dieses Template hat einen einzelnen bool-Parameter. Der ist
unsere Bedingung: ist er true (Spezialisierung in Zeile 6), dann müssen wir
die Prüfung durchführen, ist er false (Spezialisierung in Zeile 14), dann
nicht. Im Grunde genommen zeigt dieser Parameter also die Überprüfung der
Grenzen durch den Compiler an.
Wie werden diese Grenzen nun überprüft? Dazu müssen wir uns zum Beispiel den
Copy-Assignment-Operator anschauen. Dort sehen wir in Zeile 39 eine
Instanziierung des check-Templates mit einem komplexen Ausdruck in der
Parameterliste. Sämtliche Variablen in diesem Ausdruck sind
Compile-Time-Konstanten (die beiden Grenzen der eigenen Templateinstanz, wie
auch die Grenzen der anderen Instanz). Damit muss der Compiler diesen Ausdruck
auswerten können (sagt der Standard) und auf einen Wert zusammenführen (true
oder false in unserem Falle). Dieser Wert wird dann für die Instanziierung des
Templates genutzt, womit wir die passende Variante auswählen. Da unsere beiden
Template-Funktionen inline sind, wird ihr Inhalt direkt an dieser Stelle
eingesetzt (statt einen Funktionsaufruf zu generieren). Wenn unser Check also
nicht durchgeführt werden muss (die false-Spezialisierung), dann wird auch
tatsächlich überhaupt kein Code an dieser Stelle ausgeführt.
Möglich ist die Instanziierung des passenden check-Templates, weil der
Copy-Assignment-Operator seinerseits auch ein Template ist, welches seine beiden
Template-Parameter aus dem übergebenen Objekt ableitet. Jedesmal, wenn wir so
eine Zuweisung brauchen, wird also der passende Copy-Assignment-Operator
instanziiert, der wiederum das passende check-Template instanziiert, welches
dann eben prüft oder nicht.
Was wir hier mittels Template Metaprogramming implementiert haben ist eine zur
Compile-Zeit geprüfte if-Anweisung (oder ein switch mit nur zwei Pfaden).
Insgesamt geht da noch deutlich Komplexeres, aber dazu in einem anderen Video
mehr.