Wer jetzt glaubt in die Überschrift hat sich ein not eingeschlichen das dort nicht hingehört der irrt. Unzählige Bücher, Internetseiten, Foreneinträge und Mailinglisten beschäftigen sich damit wie man Code schreibt der compiliert. Hier soll es es kurz darum gehen wie man C++-Code schreibt der nicht (immer) compiliert und warum das sinnvoll sein kann.
Warum? Das dürfte sie Frage sein die sich als erstes stellt. Warum sollte man Code schreiben der nicht compiliert? Wer C/C++ kennt, kennt höchst wahrscheinlich auch assert. Das Makro assert(exp) überprüft ob der Ausdruck exp wahr oder falsch ist, wenn der Ausdruck falsch ist werden die mit atexit angegebenen Funktionen aufgerufen und das Programm danach beendet. Das ist nützlich um das Programm kontrolliert zu beenden wenn es sich in eine ausweglose Situation manövriert hat. Mit assert wird eine Bedingung zur Laufzeit überprüft, es gibt aber auch Bedingungen die bereits zu Compilezeit feststehen. In solch einem Fall ist es sinnvoll den Code nur bei erfüllter Bedingung zu compilieren, da das Programm ohnehin nicht (sinnvoll) lauffähig wäre. Man könnte auch sagen es geht darum die Menge an Code zu verringern, die syntaktisch korrekt aber semantisch falsch ist.
Für alle denen das jetzt noch zu theoretisch ist kommt hier ein Beispiel:
template<typename T,unsigned int width, unsigned int height>
class Matrix{
public:
void setToIdentity(){
for(unsigned int i; i<width; i++){
values[i*width+i]=1;
}
}
private:
T values[widht*height];
};
Die Breite und Höhe einer Matrix stehen schon beim compilieren fest. Eine Einheitsmatrix (oder Identität) ist immer quadratisch, die Funktion setToIdentity() soll also nur Funktionieren wenn die Höhe gleich der Breite ist. Wird bei einer Matrix mit ungleichen Werten versucht setToIdentity() aufgerufen, soll das Programm nicht compiliern (weil die Einheitsmatrix dann nicht definiert ist). Wir brauchen also ein Konstrukt das bei true als Wert compiliert und bei false einen Fehler verursacht.
Eine Lösung aus dem Buch Modern C++ Design:
template<bool>
struct StaticChecker{
StaticChecker(...){};
};
template<>
struct StaticChecker<false>{};
#define STATIC_CHECK(expr,msg)\
{\
class ERROR_##msg {};\
(void)sizeof(StaticChecker<(expr) != 0>((ERROR_##msg())));\
}
Diese Makro erzeugt eine lokale Klasse und übergibt diese als Parameter an den Konstruktor von StaticChecker. StaticChecker<true> hat als Parameter im Konstruktor eine Ellipse, d.h. als Parameter wird hier einfach alles akzeptiert. Ist exp aber falsch wird ein Objekt der Klasse StaticChecker<false> erzeugt und dieser template Spezialisierung fehlt der Konstruktor mit Ellipse. Der Compiler wird hier sagen das StaticChcker<false> keinen Konstruktor mit dem Parameter ERROR_msg hat. Über die Klasse ERROR_msg kann man also noch einen Hinweis geben was falsch gelaufen ist. Im Beispiel sähe das dann so aus:
void setToIdentity(){
STATIC_CHECK(width==height,WIDTH_AND_HEIGHT_NOT_EQUAL)
for(unsigned int i; i<width; i++){
values[i*width+i]=1;
}
}
Schön an dieser Lösung ist das man einen Hinweis auf den Fehler geben kann und das einen die IDE wahrscheinlich in die richtige Zeile bringt. Unschön ist aber der Rückgriff auf Makros. Meiner Meinung nach sollte man Makros nur als das verwenden was sind: als Mechanismus zur Textersetzung. Komplexere “Programmierung” sollte man nicht mit Makros machen. Makros haben z.B. Probleme wenn in dem Ausdruck ein Komma vorkommt, was die Verwendung von templates in STATIC_CHECK erschwert. Daher hier eine Alternative die auf Makros verzichtet:
template<bool>
struct COMPILE_TIME;
template<>
struct COMPILE_TIME<true>{
static void CHECK_FAILED(){};
};
template<>
struct COMPILE_TIME<false>{
};
template<bool exp>
struct CompileTimeCheck{
static void check(){COMPILE_TIME<exp>::CHECK_FAILED();};
};
Diese Lösung hat den Vorteil das sie typsicher ist und auch mit komplizierten Ausdrücken funktioniert, ein Nachteil ist das es keinen Hinweis auf die Ursache des Fehlers gibt. Hier nochmal das Beispiel mit der alternativen Lösung:
void setToIdentity(){
CompileTimeCheck<width == height>::check();
for(unsigned int i; i<width; i++){
values[i*width+i]=1;
}
}
So, ich hoffe ich konnte klar machen warum es manchmal sinnvoll sein kann dafür zu sorgen das Code nicht kompiliert.