That is not usually what an exception thrown from a constructor does.
I'm aware, but now I can't use exceptions, because of the possibility of them being disabled, which would result in termination regardless of whether an exception or an assertion was used.
I humbly re-submit the idea of using a factory function to generate your strings.
It looks like that's how it is going to be, just like in C. Though, I don't want to return a pointer to a dynamically allocated string, because I'd rather I be able to automatically manage its lifetime. This means that an invalid string might be wandering around if it fails initialization, or it might be double constructed if I tried to have a default, but valid, state. Is this the only way, return a newly allocated pointer?
Sounds to me like you've got the abstraction in the wrong place. Why add unvalidated characters?
I initially designed it after the std::basic_string class; it was mature, very commonly used, and provided features that were incredibly useful in a generic manner. As a result, it accepts pointers to character arrays, as well. I suppose I could ditch all of that, and accept only instances of the string class and instances of a unique validated character class.
It seems like an unnecessary operation to create temporary class instances for a string type that doesn't require validation. Perhaps some sort of template meta-programming that can check the traits class to see if validation is required, and then not enable the insecure functions.
You also mention needing to check a return code from a concatenation operation - why? If both operands are legitimate strings, the result will be legitimate also.
If the resulting length is greater than the maximum length allowed, or something like that, then the caller must know that the string is impossible to access in entirety, or something like that. In this case, the resulting string would be illegitimate, because it violates the maximum length.
But in cases where you can guarantee the correct data - ie. you have a type that enforces that constraint - then that type is what you should be passing in.
I suppose you're right. I'm extremely reluctant to go back to how I always was, with checking return codes everywhere. I suppose I could make more of them simply fatal errors, like triggering assertions on receiving invalid parameters that violate the contract the function requires.
These changes also mean that I need to completely redesign the class, when I was so close to finishing it. This has me entirely frustrated.