All the subclassing, and function reimplementing we did will surely look a bit crazy for beginners. Where did all the function signatures come from, of which we magically knew that we need to reimplement?
Let's first have a look where std::string really comes from:
template <
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>
>
class basic_string;
The std::string is really an std::basic_string<char> and that expands to std::basic_string<char, std::char_traits<char>, std::allocator<char>>. Okay, that is a long type description, but what does it mean? The point of all of this is that it is possible to base a string not only on single-byte char items but also on other, larger, types. This enables for string types, which can handle more than the typical American ASCII character set. This is not something we will have a look into now.
The char_traits<char> class, however, contains algorithms that basic_string needs for its operation. The knows how to compare, find, and copy characters and strings.
The allocator<char> class is also a traits class, but its special job is handling string allocation and deallocation. This is not important for us at this time as the default behavior satisfies our needs.
If we want a string class to behave differently, we can try to reuse as much as possible from what basic_string and char_traits already provide. And this is what we did. We implemented two char_traits subclasses called case_insentitive and lower_caser and configured two completely new string types with them by using them as substitutes for the standard char_traits type.