Ok, aber wieso lässt der Compiler das überhaupt zu? Ein unterschiedlicher Rückgabetyp reicht beim Überladen ja nicht aus. Reicht dafür schon das rechte const (wäre auch ein weiterer Grund, das beim unteren nicht zu schreiben)?
Das rechte const macht die Methode const und gehört, im Gegensatz zum Rückgabetyp, zur Signatur der Funktion. Wenn du eine Funktion const machst, dann sagt du damit, dass diese Funktion auf const Objekten aufgerufen werden kann.
Der untere gibt aber Zugriff auf den jeweiligen Member und erlaubt potentiell, diesen zu verändern. logical vs. binary const...
Das betrifft aber nicht direkt das rechte Funktions-const?! D.h. das könnte man theoretisch auch dazuschreiben, tut es aber der Nachvollziehbarkeit halber nicht?! Ist das mit logical const gemeint?
const auf der Methode sagt nur, dass die Methode auf Objekten aufgerufen werden kann, die const sind. Binary constness bedeutet, dass das Objekt auf Bitebene konstant bleibt, also kein einziges Bit des Objektes verändert werden kann. Von logical constness dagegen spricht man, wenn eine Methode zwar nicht wirklich den Bitpattern des Objektes konstant hält, aber das Objekt auf konzeptioneller Ebene seinen Zustand nicht ändert. Beispiel: Ein Objekt, das zum Zwecke von Debugging oder Profiling einen Zähler enthält, der zählt, wie oft eine Methode aufgerufen wird. Manche Methode ändert zwar immer noch nicht den logischen Zustand des Objektes, muss das Objekt aber auf Bitebene verändern, um den Zähler zu erhöhen. Für sowas gibt's in C++ dann das Schlüsselwort mutable. Der zweite operator[] in deinem Beispiel oben könnte übrigens nicht einfach const gemacht werden, da in einer const Methode der this Pointer vom Typ const T* und nicht T* ist und die zurückgegebene Referenz dann automatisch auch const sein müsste. Außer du castest das const weg, in dem Fall gibt's dann aber wieder Potential für Undefined Behaviour, da die Methode dann auf einem const Objekt aufgerufen werden kann, welches über die zurückgegebene Referenz dann verändert werden könnte...
Und ja, an der Pointer-Arithmetik oder dass man beim Zugriff auf undefinierte Elemente einfach Speicher-Crap anstatt irgendeinem offensichtlichen Fehlverhalten zurückkriegt, habe ich mich auch schon gestört. Trotzdem sollte das doch immer funktionieren, weil die Pointer um sizeof(T) inkrementiert werden?!
Ja, es wird mit den Typen, die du für T einsetzen willst, unter allen mir bekannten Compilern auf x86 in der Regel tun was du willst. Dennoch ist es Undefined Behaviour...