From what I can see the GString class does not do any bounds checking.
I also do not get the use of template parameter T. Could you not have used void* and cast that to uint8_t? And why not use char? Each template method generates extra code that has little/no? use.
I also do not like the Print class but I can understand why you would want to stick with that. I rewrote the Print class to a template class (TextWriter). That class implements only the knowledge on how to convert a value to text, nothing else. The Print class does that also but my TextWriter does not take up any permanent RAM bytes (Print class has virtuals/v-table and an int member) at the cost of generating more code (being a template class).
Writing a string class, you would have to give considerable thought to how you would want to manage the memory it takes to store those strings. Especially with concatenation of string. I would make a distinction between operations that work on the string itself (like toUpper) and functions that produce new strings. The latter type I would make static to drive the point home that you are creating a new string - that needs new memory. I would make the methods robust enough so you could use one parameter as the result also (copy into itself). I would
Would also be nice to encapsulate the PRGMEM strings.
Something like:
Code:
String(char* buffer, unsigned int capacity);
unsigned int getCapacity();
unsigned int getLength();
char GetCharAt(int index);
bool SetCharAt(int index, char value);
bool InsertCharAt(int index, char value);
const char* operator();
// base can be same as result - provided capacity is enough
static bool Concat(String& base, String& tail, String& result);
static bool Concat(String& base, String& mid, String& tail, String& result);
static String& Empty();
unsigned int getCapacity();
unsigned int getLength();
char GetCharAt(int index);
bool SetCharAt(int index, char value);
bool InsertCharAt(int index, char value);
const char* operator();
// base can be same as result - provided capacity is enough
static bool Concat(String& base, String& tail, String& result);
static bool Concat(String& base, String& mid, String& tail, String& result);
static String& Empty();
I would keep the class very lean (like you have done). I would perhaps make a different class to find things in strings (StringReader?). FindFirst, FindLast, IsDigit, SubString (could go both ways) and the likes.
Hope it helps.