The question of case insensitive strings has been, and continues to be asked a lot, and equally many answers can be found all over the internet. The go-to solution is to create a char_traits policy class with eq, lt, and compare methods implemented using std::toupper before comparing characters, then instantiate std::basic_string with it using istring = std::basic_string<char, char_itraits<char>> This works well until you try to use your new type anywhere near code designed with std::string in mind. Then what?

I guess what I am trying to say is that I never found a complete solution to this, so I decided to create one myself. I wanted the case insensitive string to play nicely and seamlessly with its standard counterpart. I wanted the ability to pass it as a parameter anywhere std::string is accepted; to convert between istring and std::string in both directions; push it to output stream; read it from input stream; compare it using all six operators, ==, !=, <, <=, >, >=, with std::string whether it appeared on the left or right side of the operation; and to declare literals of its type: "std::string literal"s.
In other words have it be indistinguishable from std::string except when comparisons are needed, like this:

The starting point was the character traits policy class mentioned earlier, but instead of using it with std::basic_string I inherited publicly from std::basic_string template configured with case insensitive traits policy; this pulled all its methods into the derived class scope, I only had to pull base class constructors:

All this derived class needed now was a constructor which would allow it to be created from any other type of std::basic_string as long as the character type was the same; implicit type cast operator to seamlessly convert it to std::string, and 4 comparison operators: == and <=> declared twice with istring as the first or second parameter. The constructor and comparison operators needed to be selectively enabled only for strings with different character traits policy, otherwise they would cause ambiguity and compilation errors. Final step was declaring operator >>, operator <<, and operator""_is.

P.S. Everything I just described also applies to wchar_t aka std::wstring.


The implementation on my GitHub page: istring.hpp, example program: istring.cpp.


2 Replies to “Case insensitive string, etc”

  1. If you publicly inherit from a class that does case-sensitive comparisons, your class violates the Liskov Substitution Principle (LSP). You are allowed to do private inheritance from a case sensitive string or contain one and forward member function calls.

    1. In the absence of template parameters your observation would have been correct (assuming the base class compared case-sensitively and the derived not).
      If you look closer at the template parameters of, and my class declaration…

      template<typename CharT, typename Alloc = std::allocator<CharT>>class basic_istring : public std::basic_string<CharT, char_itraits<CharT>, Alloc>

      …you will notice that the base class, std::basic_string is inherited from with possibility to use different character type and allocator (CharT and Alloc template parameters), HOWEVER the second template parameter passed to it is my char_itraits, which implements the case insensitive compare (or the functions needed by the std::basic_string to perform comparisons). THEREFORE the base class of my istring already compares case-insensitively. istring is NOT the same type as std::string. SO NO LSP VIOLATION 😉

      I could have stopped at declaring istring to be std::basic_string with my custom char_itraits class, but instead I inherited to expand the functionality; mainly the ability to create istring from std::string, and cast istring to std::string. Those are USER DEFINED TYPE CONVERSIONS, not a violation of LSP.

      Perhaps the constructor defined in istring and the type cast operator should be declared explicit; this would make the conversion (NOT SUBSTITUTION) clearer to see and to state such intent. I will consider making that change…

Leave a Reply