Teilweise ziemlich speziell
Templatespezialisierung als Möglichkeit, absichtlich Fehler hervorzurufen – die Sprache überrascht einen doch immer wieder. Diesmal geht es um die Fähigkeit der partiellen Templatespezialisierung, bei der ein Template nur für einen Teil der möglichen Werte seiner parameter spezialisiert wird. Ansonsten bleibt der Templatetyp ohne Definition unvollständig und zwingt den Compiler zu einer Fehlermeldung, wenn eine fehlende Spezialisierung gebraucht wird.
Video
Code
C++98-Variante mit partieller Templatespezialisierung
#include <iostream>
#include <stdexcept>
#include <typeinfo>
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 <bool> struct staticAssert;
template <> struct staticAssert<true>
{
};
template <int min, int max> class RangeInt
{
int mValue;
RangeInt(int value, bool)
{
mValue = value;
}
template <int otherMin, int otherMax> friend class RangeInt;
public:
RangeInt(int value)
{
check<true>(value, min, max);
mValue = value;
}
template <int otherMin, int otherMax>
RangeInt(RangeInt<otherMin, otherMax> const &other)
{
staticAssert< (otherMin <= max && otherMax >= min) >();
check< (otherMin < min || otherMax > max) >(other.getValue(), min, max);
mValue = other.getValue();
}
template <int otherMin, int otherMax>
RangeInt &operator=(RangeInt<otherMin, otherMax> const &other)
{
staticAssert< (otherMin <= max && otherMax >= min) >();
check< (otherMin < min || otherMax > max) >(other.getValue(), min, max);
mValue = other.getValue();
return *this;
}
template <int otherMin, int otherMax> RangeInt<min+otherMin, max+otherMax>
operator+(RangeInt<otherMin, otherMax> const &other)
{
return RangeInt<min+otherMin, max+otherMin>(other.getValue()+mValue, true);
}
operator int() const
{
return mValue;
}
int getValue() const
{
return mValue;
}
};
int main()
{
RangeInt<5, 20> r1(10);
RangeInt<15, 30> r2(25);
r1 = r2;
}
C++-11-Variante mit static_assert
#include <iostream>
#include <stdexcept>
#include <type_traits>
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;
RangeInt(int value, bool)
{
mValue = value;
}
template <int otherMin, int otherMax> friend class RangeInt;
public:
RangeInt(int value)
{
check<true>(value, min, max);
mValue = value;
}
template <int otherMin, int otherMax>
RangeInt(RangeInt<otherMin, otherMax> const &other)
{
static_assert(otherMin <= max && otherMax >= min, "Initialisierung kann niemals erfolgreich sein!");
check< (otherMin < min || otherMax > max) >(other.getValue(), min, max);
mValue = other.getValue();
}
template <int otherMin, int otherMax>
RangeInt &operator=(RangeInt<otherMin, otherMax> const &other)
{
static_assert(otherMin <= max && otherMax >= min, "Zuweisung kann niemals erfolgreich sein!");
check< (otherMin < min || otherMax > max) >(other.getValue(), min, max);
mValue = other.getValue();
return *this;
}
template <int otherMin, int otherMax> RangeInt<min+otherMin, max+otherMax>
operator+(RangeInt<otherMin, otherMax> const &other)
{
return RangeInt<min+otherMin, max+otherMin>(other.getValue()+mValue, true);
}
operator int() const
{
return mValue;
}
int getValue() const
{
return mValue;
}
};
int main()
{
RangeInt<5, 20> r1(10);
RangeInt<25, 30> r2(25);
r1 = r2;
}
Erklärung
Der ganze Trick versteckt sich im staticAssert-Template. In Zeile 19 wird
dieses nur deklariert, aber ohne Definition gelassen. Für den Wert true wird
es dann direkt darunter spezialisiert und mit einer passenden Definition
versehen. Die ist leer, denn sie tut zur Laufzeit eigentlich gar nichts. Wichtig
ist nur, dass sie existiert.
Die Verwendung findet sich dann in Zeile 54. Ähnlich wie beim check-Template
wird die Bedingung, die geprüft werden soll, als Ausdruck für den
Template-Parameter spezifiziert. Trifft der Compiler auf diesen Ausdruck, so
muss er ihn natürlich berechnen, um die passende Spezialisierung für das
Template auswählen zu können. Aus diesem Grund dürfen im Ausdruck natürlich
wieder nur Compiler-Time-Konstanten vorkommen. Der Ausdruck, den wir hier
gewählt haben, gibt an, dass sich die Wertebereiche von Quelle und Ziel
mindestens auf einer Seite überlappen müssen. Das ist notwendig, damit es
überhaupt einen gemeinsamen Wertebereich gibt, in dem eine Zuweisung klappen
könnte.
Wertet der angegebene Ausdruck zu true aus, dann wird die – vorhandene –
true-Spezialisierung ausgewählt, das Template instanziiert und der
Compilerlauf fortgesetzt. Wertet der Ausdruck hingegen zu false aus, müsste
der Compiler das Template eben dafür instanziieren. Das kann er aber nicht, da
wir weder eine allgemeine Definition, noch eine Spezialisierung für false für
das Template angegeben haben. Der gewünschte Typ ist also unvollständig, da ihm
die Definition fehlt, genau die Fehlermeldung, die uns der Compiler auch um die
Ohren wirft.
Der Trick funktioniert, weil der Compiler erst bei der Instanziierung eines
Templates auf mögliche Fehler prüft. Solange keine unserer Zuweisungen im
Programm die false-Spezialisierung instanziiert, stört deren Fehlen den
Compiler kein Stück.
Die Fähigkeit, die Übersetzung abhängig von bestimmten Bedingungen abbrechen zu
können, ist in so vielen Fällen praktisch, dass das Standardisierungskommitee
ein entsprechendes Sprachmittel in C++11 aufgenommen hat. Mittels
static_assert kann genauso ein Compile-Time-Ausdruck geprüft und ggf. ein
Fehler ausgelöst werden. Etwas praktischer hier noch: man kann die Fehlermeldung
des Compilers bestimmen. static_assert nimmt einen String als zweiten
Parameter und gibt diesen im Fehlerfall aus. Das hilft dem geneigten
Programmierer vielleicht schneller auf die Sprünge, als eine Meldung über einen
unvollständigen Typ...