Recently I’ve been asked how you can make method find of std::string case insensitive. An obvious solution is to transform the strings to lower case, for instance, and perform the search in lower case. The solution has some inconvenients and a clean and nice solution is defining custom traits that ignore the case. The solution I’m showing bellow is based onĀ an example from the book “The C++ Standard Library – A Tutorial and Reference” by Nicolai M. Josuttis (Addison Wesley, 1999). The original example is available online on the author’s web page: http://www.josuttis.com/libbook/string/icstring1.cpp.html.

What I’m showing below is a modified implementation that allows working both with strings of char and wchar_t.

For the start, a custom char_traits implementation must be provided:

// define a custom traits class that ignores case
template struct ignorecase_traits : public std::char_traits
{
	typedef T		char_type;
	typedef int		int_type;
	typedef std::streamoff	off_type;
	typedef std::streampos	pos_type;
	typedef std::mbstate_t	state_type;

	// Performs the assignment dst = src
	static void assign(char_type& dst, const char_type src)
	{
		dst = src;
	}

	// Assigns the value c to each pointer in the range [dst, dst+n), and returns dst
	static char_type* assign(char_type* dst, size_t n, char_type c)
	{
		return static_cast(std::memset(dst, n, c));
	}

	// return whether c1 and c2 are equal
	static bool eq(const char_type& c1, const char_type& c2)
	{
		return std::toupper(c1)==std::toupper(c2);
	}

	// return whether c1 is less than c2
	static bool lt(const char_type& c1, const char_type& c2)
	{
		return std::toupper(c1) < std::toupper(c2);
	}

	// compare up to n characters of s1 and s2
	static int compare(const char_type* s1, const char_type* s2, std::size_t n)
	{
		for (std::size_t i=0; i < n; ++i)
		{
			if (!eq(s1[i], s2[i]))
			{
				return lt(s1[i], s2[i])?-1:1;
			}
		}
		return 0;
	}

	// search c in s
	static const char_type* find(const char_type* s, std::size_t n, const char_type& c)
	{
		for (std::size_t i=0; i < n; ++i)
		{
			if (eq(s[i], c))
			{
				return &(s[i]);
			}
		}
		return 0;
	}
};



Structure ignorecase_traits is parameterized, so that we will be able to instantiate it both for char and wchar_t. Two typedefs of basic_string with the new ignorechar_traits are necessary:

// define a special type for a string of char
typedef std::basic_string> icstring;

// define a special type for a strings of wchar_t
typedef std::basic_string> icwstring;



To be able to print objects of type icstring or icwstring an overload of operator<< is required:

template 
inline std::basic_ostream& operator << (std::basic_ostream& strm,
    const std::basic_string>& s)
{
	return strm << s.c_str();
}



Having all that implemented, we can use the two types, like the shown below:

int _tmain(int argc, _TCHAR* argv[])
{
	icstring is1("marius");
	icstring is2("marIUS");

	std::cout << std::boolalpha;
	std::cout << is1 << " == " << is2 << " : " << (is1==is2) << std::endl;

	icwstring iws1(L"bancila");
	icwstring iws2(L"banCILA");

	std::wcout << std::boolalpha;
	std::wcout << iws1 << L" == " << iws2 << L" : " << (iws1==iws2) << std::endl;

	if(is2.find("Riu") != std::string::npos)
		std::cout << "Riu found in " << is2 << std::endl;

	std::wstring ws1(L"cil");
	icwstring iws3(ws1.begin(), ws1.end());

	if(iws2.find(iws3) != std::string::npos)
		std::wcout << iws3 << L" found in " << iws2 << std::endl;

	return 0;
}



If you run the code you'll get the following output:

marius == marIUS : true
bancila == banCILA : true
Riu found in marIUS
cil found in banCILA


Hits for this post: 4274 .