3

Case insensitive string, etc

 3 years ago
source link: https://vorbrodt.blog/2021/04/10/case-insensitive-string-etc/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Case insensitive string, etc

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:

auto f_std = [](const string& s) {};
auto f_is = [](const istring& s) {};
auto std1 = string{"abc"};
auto istr1 = istring{"ABC"};
auto istr2 = "istring literal"_is;
f_std(istr1);
f_is(std1);
auto result = (std1 == istr1);
std1 = istr1;
istr1 = std1;

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:

template<typename CharT, typename Alloc = std::allocator<CharT>>
class basic_istring : public std::basic_string<CharT, char_itraits<CharT>, Alloc>
public:
using base = std::basic_string<CharT, char_itraits<CharT>, Alloc>;
using base::base; // use 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.


#pragma once
#include <istream>
#include <ostream>
#include <compare>
#include <string>
#include <locale>
#include <utility>
#include <algorithm>
#include <type_traits>
inline namespace detail
template<typename CharT>
inline auto char_ieq(CharT c1, CharT c2, const std::locale& loc = std::locale())
return std::toupper(c1, loc) == std::toupper(c2, loc);
template<typename CharT>
inline auto char_ilt(CharT c1, CharT c2, const std::locale& loc = std::locale())
return std::toupper(c1, loc) < std::toupper(c2, loc);
template<typename CharT>
inline auto string_icmp(const CharT* s1, std::size_t n1, const CharT* s2, std::size_t n2, const std::locale& loc = std::locale())
if(std::lexicographical_compare(s1, s1 + n1, s2, s2 + n2, [&](CharT c1, CharT c2) { return char_ilt(c1, c2, loc); })) return -1;
if(std::lexicographical_compare(s2, s2 + n2, s1, s1 + n1, [&](CharT c1, CharT c2) { return char_ilt(c1, c2, loc); })) return 1;
return 0;
template<typename CharT>
struct char_itraits : std::char_traits<CharT>
static auto eq(CharT c1, CharT c2)
return char_ieq(c1, c2);
static auto lt(CharT c1, CharT c2)
return char_ilt(c1, c2);
static auto compare(const CharT* s1, const CharT* s2, std::size_t n)
return string_icmp(s1, n, s2, n);
template<typename CharT, typename Alloc = std::allocator<CharT>>
class basic_istring : public std::basic_string<CharT, char_itraits<CharT>, Alloc>
public:
using base = std::basic_string<CharT, char_itraits<CharT>, Alloc>;
using base::base;
template<typename Traits2, typename Alloc2,
std::enable_if_t<not std::is_same_v<char_itraits<CharT>, Traits2>, void>* = nullptr>
basic_istring(const std::basic_string<CharT, Traits2, Alloc2>& str)
: base(str.data(), str.length()) {}
operator auto () const
return std::basic_string<CharT>(this->data(), this->length());
template<typename Traits2, typename Alloc2>
std::enable_if_t<not std::is_same_v<char_itraits<CharT>, Traits2>, bool>
friend operator == (const basic_istring& lhs, std::basic_string<CharT, Traits2, Alloc2>& rhs)
return string_icmp(lhs.data(), lhs.length(), rhs.data(), rhs.length()) == 0;
template<typename Traits2, typename Alloc2>
std::enable_if_t<not std::is_same_v<char_itraits<CharT>, Traits2>, std::strong_ordering>
friend operator <=> (const basic_istring& lhs, std::basic_string<CharT, Traits2, Alloc2>& rhs)
return string_icmp(lhs.data(), lhs.length(), rhs.data(), rhs.length()) <=> 0;
template<typename Traits2, typename Alloc2>
std::enable_if_t<not std::is_same_v<char_itraits<CharT>, Traits2>, bool>
friend operator == (std::basic_string<CharT, Traits2, Alloc2>& lhs, const basic_istring& rhs)
return string_icmp(lhs.data(), lhs.length(), rhs.data(), rhs.length()) == 0;
template<typename Traits2, typename Alloc2>
std::enable_if_t<not std::is_same_v<char_itraits<CharT>, Traits2>, std::strong_ordering>
friend operator <=> (std::basic_string<CharT, Traits2, Alloc2>& lhs, const basic_istring& rhs)
return string_icmp(lhs.data(), lhs.length(), rhs.data(), rhs.length()) <=> 0;
using istring = basic_istring<char>;
using iwstring = basic_istring<wchar_t>;
inline auto& operator >> (std::istream& is, istring& istr)
std::string temp;
is >> temp;
istr = std::move(temp);
return is;
inline auto& operator >> (std::wistream& wis, iwstring& iwstr)
std::wstring temp;
wis >> temp;
iwstr = std::move(temp);
return wis;
inline auto& operator << (std::ostream& os, const istring& istr)
os << istr.c_str();
return os;
inline auto& operator << (std::wostream& wos, const iwstring& iwstr)
wos << iwstr.c_str();
return wos;
inline auto operator ""_is(const char* istr, std::size_t len)
return istring(istr, len);
inline auto operator ""_iws(const wchar_t* iwstr, std::size_t len)
return iwstring(iwstr, len);

Like this:

Loading...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK