Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Comparison operator used in STL/deque

92 views
Skip to first unread message

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 5:07:51 AM4/6/23
to
I'm writing a small case insensitive string class. Here's some of the code:

struct ci_string: public string
{
using string::string;

bool operator==(const ci_string &rhs)
{
cout << "DEBUG 1\n";
return !strcasecmp(c_str(),rhs.c_str());
}

bool operator==(const char *rhs)
{
cout << "DEBUG 2\n";
return !strcasecmp(c_str(),rhs);
}
:
:
};

However when I use it in a container neither of the comparison operators
above get called. eg:

deque<ci_string> dq1 = { "Hello", "cruel", "WORLD" };
deque<ci_string> dq2 = { "Hello", "cruel", "WORLD" };
cout << (dq1 == dq2) << endl;

This returns zero unless the capitisation is the same so it looks like for
some reason its calling std::string operator==() instead of my overloaded
ones. I've tried templating it too, eg:

template<typename T> bool operator==(T rhs)
{
return !strcasecmp(c_str(),rhs);
}

That didn't work either.

What have I done wrong? This is really starting to annoy me.


Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 5:09:09 AM4/6/23
to
On Thu, 6 Apr 2023 09:07:35 -0000 (UTC)
Mut...@dastardlyhq.com wrote:
> template<typename T> bool operator==(T rhs)
> {
> return !strcasecmp(c_str(),rhs);

That should have been:

return !strcasecmp(c_str(),rhs.c_str());


Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 5:33:01 AM4/6/23
to
On Thu, 6 Apr 2023 09:07:35 -0000 (UTC)
Mut...@dastardlyhq.com wrote:
>What have I done wrong?

Turns out the STL doesn't call operator==(), it calls compare() which needs
to be overloaded. Grrrr!


Malcolm McLean

unread,
Apr 6, 2023, 5:46:25 AM4/6/23
to
The convention is to supply the "less than" (<) operator.
Algorithms test for equality by testing that both permutations of "less than"
return false.

Paavo Helde

unread,
Apr 6, 2023, 6:22:56 AM4/6/23
to
06.04.2023 12:07 Mut...@dastardlyhq.com kirjutas:
> I'm writing a small case insensitive string class. Here's some of the code:
>
> struct ci_string: public string
> {

Publicly deriving from std::string is not a good idea at all as it would
silently convert to std::string&, which is a major no-no.

To really write a case insensitive string class, you need to replace
character traits:

struct ci_string_traits: public std::char_traits<char> {
// define the needed member functions like compare()
};

using ci_string = std::basic_string<char, ci_string_traits,
std::allocator<char>>;




Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 9:44:24 AM4/6/23
to
On Thu, 6 Apr 2023 13:22:38 +0300
Paavo Helde <ees...@osa.pri.ee> wrote:
>06.04.2023 12:07 Mut...@dastardlyhq.com kirjutas:
>> I'm writing a small case insensitive string class. Here's some of the code:
>>
>> struct ci_string: public string
>> {
>
>Publicly deriving from std::string is not a good idea at all as it would
>silently convert to std::string&, which is a major no-no.

When would it convert?

>To really write a case insensitive string class, you need to replace
>character traits:

I really don't understand why std::string doesn't at least have a case
insensitive equality/compare function. Is this asking too much in 2023?

>struct ci_string_traits: public std::char_traits<char> {
> // define the needed member functions like compare()
>};
>
>using ci_string = std::basic_string<char, ci_string_traits,
>std::allocator<char>>;

Ugh.

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 9:54:11 AM4/6/23
to
I tried overloading <, >, ==, !=. Nothing worked.

Overloading == worked for direct comparisons between ci_string objects but
when comparing STL containers containing ci_string it didn't.

Sometimes I can understand why some people just give up with C++ and use
something else. It really shouldn't be this hard to do something this simple.

Daniel

unread,
Apr 6, 2023, 10:08:06 AM4/6/23
to
On Thursday, April 6, 2023 at 9:44:24 AM UTC-4, Mut...@dastardlyhq.com wrote:

> I really don't understand why std::string doesn't at least have a case
> insensitive equality/compare function. Is this asking too much in 2023?

Yes :-)

In 2023, there would be little point in having a case insensitive compare function where only ASCII letters could be compared in a case-insensitive way. That's all strcasecmp does. I follow the WG21 SG16 Unicode and text study group, and it seems to be proceeding at a glacial pace, maybe in ten years Standard C++ will have some standard library Unicode algorithms.

Daniel

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 10:13:32 AM4/6/23
to
On Thu, 6 Apr 2023 07:07:55 -0700 (PDT)
Daniel <daniel...@gmail.com> wrote:
>On Thursday, April 6, 2023 at 9:44:24=E2=80=AFAM UTC-4, Mut...@dastardlyhq.=
>com wrote:
>
>> I really don't understand why std::string doesn't at least have a case=20
>> insensitive equality/compare function. Is this asking too much in 2023?
>
>Yes :-)
>
>In 2023, there would be little point in having a case insensitive compare f=
>unction where only ASCII letters could be compared in a case-insensitive wa=

Even ascii only would be better that nothing.

>y. That's all strcasecmp does. I follow the WG21 SG16 Unicode and text stud=

Yet someone decided it was worth putting strcasecmp() in POSIX. Perhaps the
C++ committee should take note.

Its not a big deal if all you want to do is compare 2 strings, the problem
comes when you want to compare/search/find in containers in a case insensitive
way.


Paavo Helde

unread,
Apr 6, 2023, 10:39:47 AM4/6/23
to
06.04.2023 16:44 Mut...@dastardlyhq.com kirjutas:
> On Thu, 6 Apr 2023 13:22:38 +0300
> Paavo Helde <ees...@osa.pri.ee> wrote:
>> 06.04.2023 12:07 Mut...@dastardlyhq.com kirjutas:
>>> I'm writing a small case insensitive string class. Here's some of the code:
>>>
>>> struct ci_string: public string
>>> {
>>
>> Publicly deriving from std::string is not a good idea at all as it would
>> silently convert to std::string&, which is a major no-no.
>
> When would it convert?

Whenever it gets passed to a function taking a (const) std::string&
reference.

>
>> To really write a case insensitive string class, you need to replace
>> character traits:
>
> I really don't understand why std::string doesn't at least have a case
> insensitive equality/compare function. Is this asking too much in 2023?

The std::string interface is too bloated already. There is no need to
make every function a member function, especially if there is no
consensus how it should behave.

In particular, a case insensitive string comparison should define the
upper and lower case characters which are collided. Would only ASCII
range be considered (which would be clearly inadequate in an ISO
standard), full Unicode charts, or something in-between? The question of
code pages would come up immediately, and different encodings like utf-8
vs Shift-JIS, etc.

Nobody wants to open this can of worms, including the C++ standard
committee.

>
>> struct ci_string_traits: public std::char_traits<char> {
>> // define the needed member functions like compare()
>> };
>>
>> using ci_string = std::basic_string<char, ci_string_traits,
>> std::allocator<char>>;
>
> Ugh.

Why ugh? That's what the char_traits are for, and pretty easy to
implement in case you only want case insensitivity in ASCII range (as I
suspect). Note that you should not use strcasecmp() as it is locale
dependent and thus might potentially not be ASCII, and it is also
potentially slower than an ASCII-only comparison.

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 10:47:55 AM4/6/23
to
On Thu, 6 Apr 2023 17:39:30 +0300
Paavo Helde <ees...@osa.pri.ee> wrote:
>06.04.2023 16:44 Mut...@dastardlyhq.com kirjutas:
>> On Thu, 6 Apr 2023 13:22:38 +0300
>> Paavo Helde <ees...@osa.pri.ee> wrote:
>>> 06.04.2023 12:07 Mut...@dastardlyhq.com kirjutas:
>>>> I'm writing a small case insensitive string class. Here's some of the code:
>
>>>>
>>>> struct ci_string: public string
>>>> {
>>>
>>> Publicly deriving from std::string is not a good idea at all as it would
>>> silently convert to std::string&, which is a major no-no.
>>
>> When would it convert?
>
>Whenever it gets passed to a function taking a (const) std::string&
>reference.

Well sure, but that goes for any child class being passed as a base class.
In this case it shouldn't matter as I don't have any child specific data, its
simply overloaded operators so it doesn't matter about no virtual destructor
etc.

>>> struct ci_string_traits: public std::char_traits<char> {
>>> // define the needed member functions like compare()
>>> };
>>>
>>> using ci_string = std::basic_string<char, ci_string_traits,
>>> std::allocator<char>>;
>>
>> Ugh.
>
>Why ugh? That's what the char_traits are for, and pretty easy to

Because I wouldn't get all the non member operator overloads I get for free
with std::string, particularly the stream stuff.

To be honest I've given up. Its so much work no matter what approach I use
that I might as well write my own string class from scratch.

Daniel

unread,
Apr 6, 2023, 10:48:40 AM4/6/23
to
On Thursday, April 6, 2023 at 10:13:32 AM UTC-4, Mut...@dastardlyhq.com wrote:

> Its not a big deal if all you want to do is compare 2 strings, the problem
> comes when you want to compare/search/find in containers in a case insensitive
> way.

Just define some function objects that compare strings in any way that you
want, e.g.

struct InsensitiveCaseCompare
{
bool operator()(const std::string& s1, const std::string& s2) const
{
return strcasecmp(s1.c_str(), s2.c_str()) < 0;
}
};

Then use them in the associative containers, e.g.

std::map<std::string, int, InsensitiveCaseCompare> m;

and you'll have a map that does a case insensitive compare of ASCII letters.

Daniel

Bonita Montero

unread,
Apr 6, 2023, 10:56:29 AM4/6/23
to
Write your own traits:

#include <iostream>
#include <string>
#include <type_traits>

using namespace std;

template<typename Elem>
class icase_traits : public char_traits<Elem>
{
static constexpr Elem tolower( Elem c ) noexcept
{
return c < 'A' || c > 'Z' ? c : c - ('A' - 'a');
}
public:
static constexpr bool eq( Elem a, Elem b ) noexcept
{
return tolower( a ) == tolower( b );
}
static constexpr bool lt( Elem a, Elem b ) noexcept
{
return tolower( a ) < tolower( b );
}
static int compare( Elem const *s1, Elem const *s2, size_t count )
{
for( size_t i = 0; i != count; ++i )
if( tolower( s1[i] ) == tolower( s2[i] ) )
continue;
else if( tolower( s1[i] ) < tolower( s2[i] ) )
return -1;
else
return 1;
return 0;
}
};

template<typename Elem>
using icase_string = basic_string<Elem, icase_traits<Elem>,
allocator<Elem>>;

int main()
{
icase_string<char>
strA( "hello" ),
strB( "Hello" );
cout << (strA == strB) << endl;
}

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 11:06:00 AM4/6/23
to
On Thu, 6 Apr 2023 07:48:28 -0700 (PDT)
Daniel <daniel...@gmail.com> wrote:
>On Thursday, April 6, 2023 at 10:13:32=E2=80=AFAM UTC-4, Mut...@dastardlyhq=
>..com wrote:
>
>> Its not a big deal if all you want to do is compare 2 strings, the proble=
>m=20
>> comes when you want to compare/search/find in containers in a case insens=
>itive=20
>> way.
>
>Just define some function objects that compare strings in any way that you
>want, e.g.
>
>struct InsensitiveCaseCompare
>{
> bool operator()(const std::string& s1, const std::string& s2) const
> {
> return strcasecmp(s1.c_str(), s2.c_str()) < 0;
> }
>};
>
>Then use them in the associative containers, e.g.
>
>std::map<std::string, int, InsensitiveCaseCompare> m;

The fact that even though lambdas have been around for 12 years functors
still have to be used for comparison operations in the STL is a bit pathetic.
You'd think compilers by now could cope with something like:

map<string, int, decltype([](const string& s1, const string& s2) { strcasecmp(s1
c_str(), s2.c_str()) < 0 }) m;

>
>and you'll have a map that does a case insensitive compare of ASCII letters=
>..

Sure, but I need to compare the strings directly too. I'd rather it was all
wrapped up in 1 type. Not have to the above for containers and then call
strcasecmp(s1.c_str(),s2.c_str()) all over the place. Might as well be using C.

What a mess C++ is sometimes.

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 11:10:44 AM4/6/23
to
On Thu, 6 Apr 2023 16:58:07 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>Write your own traits:
> cout << (strA == strB) << endl;

Now do

cout << strA << endl;

And see how well it compiles.

Yes, one can easily write the overload. The point of inheritence is you
shouldn't have to do all this boilerplate shit.

Daniel

unread,
Apr 6, 2023, 11:17:07 AM4/6/23
to
Yes. Many standard C++ utilities apply only to standard specializations
including std::string, std::u8string, std::u16string, and so on, and not to
user specializations of std::basic_string. If for example I specialize
std::basic_string with a custom allocator, I no longer have a standard
library specialization of std::hash, even though Standard C++ could
have easily specified one for the general case (some compilers
provide one.)

Daniel

Bonita Montero

unread,
Apr 6, 2023, 11:40:44 AM4/6/23
to
Use strA.c_str().

Bonita Montero

unread,
Apr 6, 2023, 11:50:46 AM4/6/23
to
This is more performant:

#include <iostream>
#include <string>
#include <type_traits>

using namespace std;

template<typename Elem>
struct icase_traits : public char_traits<Elem>
{
using char_type = char_traits<Elem>::char_type;
private:
static constexpr Elem tolower( char_type c ) noexcept
{
return c < 'A' || c > 'Z' ? c : c - ('A' - 'a');
}
public:
static constexpr bool eq( char_type a, char_type b ) noexcept
{
return tolower( a ) == tolower( b );
}
static constexpr bool lt( char_type a, char_type b ) noexcept
{
return tolower( a ) < tolower( b );
}
static constexpr int compare( char_type const *s1, char_type const *s2,
size_t count )
{
for( size_t i = 0; i != count; ++i )
{
char_type
a = tolower( s1[i] ),
b = tolower( s2[i] );
if( a == b ) [[likely]]
continue;
else
if constexpr( is_integral_v<char_type> && sizeof(char_type) <
sizeof(int) )
return (int)a - (int)b;
else
return a < b ? -1 : 1;
}
return 0;
}
};

template<typename Elem>
using icase_string = basic_string<Elem, icase_traits<Elem>,
allocator<Elem>>;

int main()
{
icase_string<char>
strA( "hello" ),
strB( "Hello" );
cout << (strA == strB) << endl;
cout << string( "hello word" ) << endl;
}

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 11:57:22 AM4/6/23
to
On Thu, 6 Apr 2023 17:42:21 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>Am 06.04.2023 um 17:10 schrieb Mut...@dastardlyhq.com:
>> On Thu, 6 Apr 2023 16:58:07 +0200
>> Bonita Montero <Bonita....@gmail.com> wrote:
>>> Write your own traits:
>>> cout << (strA == strB) << endl;
>>
>> Now do
>>
>> cout << strA << endl;
>>
>> And see how well it compiles.
>>
>> Yes, one can easily write the overload. The point of inheritence is you
>> shouldn't have to do all this boilerplate shit.
>>
>
>Use strA.c_str().

Bit of an ugly hack really. Shouldn't be necessary.

Mut...@dastardlyhq.com

unread,
Apr 6, 2023, 12:00:42 PM4/6/23
to
On Thu, 6 Apr 2023 17:52:23 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>This is more performant:
> static constexpr int compare( char_type const *s1, char_type const *s2,
>size_t count )

I don't know how compare() works in this case , but presumably "count" is the
shortest length of the two strings? Otherwise it would be wise to check for \0.

Bonita Montero

unread,
Apr 6, 2023, 12:04:57 PM4/6/23
to
Am 06.04.2023 um 17:57 schrieb Mut...@dastardlyhq.com:

> On Thu, 6 Apr 2023 17:42:21 +0200

>> Use strA.c_str().

> Bit of an ugly hack really. Shouldn't be necessary.

Then you have a lot of work if you do it your way.
I prefer this easier way which I don't consider as ugly.

Bonita Montero

unread,
Apr 6, 2023, 12:06:19 PM4/6/23
to
Am 06.04.2023 um 18:00 schrieb Mut...@dastardlyhq.com:

> I don't know how compare() works in this case , but presumably "count" is the
> shortest length of the two strings? ...

That's what I also assumed. The caller is responsible to
decide what to do if both strings are equal up to count.

Daniel

unread,
Apr 6, 2023, 12:27:24 PM4/6/23
to
On Thursday, April 6, 2023 at 12:00:42 PM UTC-4, Mut...@dastardlyhq.com wrote:
> On Thu, 6 Apr 2023 17:52:23 +0200
> Bonita Montero <Bonita....@gmail.com> wrote:
> >This is more performant:
> > static constexpr int compare( char_type const *s1, char_type const *s2,
> >size_t count )
> I don't know how compare() works in this case , but presumably "count" is the
> shortest length of the two strings?

Yes. See

https://en.cppreference.com/w/cpp/string/basic_string/compare

for an explanation of how std::basic_string::compare uses std::char_traits::compare.

If std::char_traits::compare returns 0, std::basic_string::compare
returns 0 if the two strings are the same size, < 0 if the first string is shorter,
and > 0 if the second string is shorter.

Daniel

Bo Persson

unread,
Apr 6, 2023, 1:24:12 PM4/6/23
to
On 2023-04-06 at 16:13, Mut...@dastardlyhq.com wrote:
> On Thu, 6 Apr 2023 07:07:55 -0700 (PDT)
> Daniel <daniel...@gmail.com> wrote:
>> On Thursday, April 6, 2023 at 9:44:24=E2=80=AFAM UTC-4, Mut...@dastardlyhq.=
>> com wrote:
>>
>>> I really don't understand why std::string doesn't at least have a case=20
>>> insensitive equality/compare function. Is this asking too much in 2023?
>>
>> Yes :-)
>>
>> In 2023, there would be little point in having a case insensitive compare f=
>> unction where only ASCII letters could be compared in a case-insensitive wa=
>
> Even ascii only would be better that nothing.
>

Who says std::string uses ASCII?


Scott Lurndal

unread,
Apr 6, 2023, 1:36:58 PM4/6/23
to
Given ASCII is a proper subset of UTF-8....

Granted there are degenerate operating systems that still use 16-bit
wide characters, but that's an abberation :-).

Daniel

unread,
Apr 6, 2023, 3:21:33 PM4/6/23
to
On Thursday, April 6, 2023 at 1:36:58 PM UTC-4, Scott Lurndal wrote:
> Bo Persson <b...@bo-persson.se> writes:
> >On 2023-04-06 at 16:13, Mut...@dastardlyhq.com wrote:
> >> On Thu, 6 Apr 2023 07:07:55 -0700 (PDT)
> >> Daniel <daniel...@gmail.com> wrote:
> >>> On Thursday, April 6, 2023 at 9:44:24=E2=80=AFAM UTC-4, Mut...@dastardlyhq.=
> >>> com wrote:
> >>>
> >>>> I really don't understand why std::string doesn't at least have a case=20
> >>>> insensitive equality/compare function. Is this asking too much in 2023?
> >>>
> >>> Yes :-)
> >>>
> >>> In 2023, there would be little point in having a case insensitive compare f=
> >>> unction where only ASCII letters could be compared in a case-insensitive wa=
> >>
> >> Even ascii only would be better that nothing.
> >>
> >
> >Who says std::string uses ASCII?
> >
> Given ASCII is a proper subset of UTF-8....

True, but I thought Bo's point was that since the bytes in a std::string could be in
any 8-bit encoding , could be ASCII, UTF-8, ISO 8859-1, GB2312, EBCDIC,
it would be problematic to add functions to std::string that assume a particular
encoding (such as ASCII.)
>
> Granted there are degenerate operating systems that still use 16-bit
> wide characters, but that's an abberation :-).

What do you mean by "character"? The term "character" isn't defined by Unicode.

In a modern language, I think a char or character would most sensibly be
defined as a Unicode Scalar Value (as it is in rust.)

Daniel





Keith Thompson

unread,
Apr 6, 2023, 3:44:34 PM4/6/23
to
Daniel <daniel...@gmail.com> writes:
> On Thursday, April 6, 2023 at 1:36:58 PM UTC-4, Scott Lurndal wrote:
>> Bo Persson <b...@bo-persson.se> writes:
>> >On 2023-04-06 at 16:13, Mut...@dastardlyhq.com wrote:
>> >> On Thu, 6 Apr 2023 07:07:55 -0700 (PDT)
>> >> Daniel <daniel...@gmail.com> wrote:
[...]
>> >>> In 2023, there would be little point in having a case insensitive compare f=
>> >>> unction where only ASCII letters could be compared in a case-insensitive wa=
>> >>
>> >> Even ascii only would be better that nothing.
>> >>
>> >
>> >Who says std::string uses ASCII?
>> >
>> Given ASCII is a proper subset of UTF-8....
>
> True, but I thought Bo's point was that since the bytes in a std::string could be in
> any 8-bit encoding , could be ASCII, UTF-8, ISO 8859-1, GB2312, EBCDIC,
> it would be problematic to add functions to std::string that assume a particular
> encoding (such as ASCII.)

Who says a byte is 8 bits?

[...]

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for XCOM Labs
void Void(void) { Void(); } /* The recursive call of the void */

Daniel

unread,
Apr 6, 2023, 4:12:26 PM4/6/23
to
On Thursday, April 6, 2023 at 3:44:34 PM UTC-4, Keith Thompson wrote:
> Daniel <daniel...@gmail.com> writes:
> > True, but I thought Bo's point was that since the bytes in a std::string could be in
> > any 8-bit encoding , could be ASCII, UTF-8, ISO 8859-1, GB2312, EBCDIC,
> > it would be problematic to add functions to std::string that assume a particular
> > encoding (such as ASCII.)
> Who says a byte is 8 bits?
>
Well, nobody said that :-) The reference to number of bits referred to the encodings.
Admittedly, ASCII is a 7 bit encoding.

As a long as a C++ byte is at least 8 bits, it can hold an encoding that requires 7 or 8 bits.

Daniel

Chris M. Thomasson

unread,
Apr 6, 2023, 5:38:20 PM4/6/23
to
On 4/6/2023 12:44 PM, Keith Thompson wrote:
> Daniel <daniel...@gmail.com> writes:
>> On Thursday, April 6, 2023 at 1:36:58 PM UTC-4, Scott Lurndal wrote:
>>> Bo Persson <b...@bo-persson.se> writes:
>>>> On 2023-04-06 at 16:13, Mut...@dastardlyhq.com wrote:
>>>>> On Thu, 6 Apr 2023 07:07:55 -0700 (PDT)
>>>>> Daniel <daniel...@gmail.com> wrote:
> [...]
>>>>>> In 2023, there would be little point in having a case insensitive compare f=
>>>>>> unction where only ASCII letters could be compared in a case-insensitive wa=
>>>>>
>>>>> Even ascii only would be better that nothing.
>>>>>
>>>>
>>>> Who says std::string uses ASCII?
>>>>
>>> Given ASCII is a proper subset of UTF-8....
>>
>> True, but I thought Bo's point was that since the bytes in a std::string could be in
>> any 8-bit encoding , could be ASCII, UTF-8, ISO 8859-1, GB2312, EBCDIC,
>> it would be problematic to add functions to std::string that assume a particular
>> encoding (such as ASCII.)
>
> Who says a byte is 8 bits?
>
> [...]
>

This does: A bit of sarcasm.
__________________
#include <iostream>
#include <cstdint>
#include <climits>


typedef std::uint8_t byte;


#if ((UINT8_MAX != 255UL) || (CHAR_BIT != 8))
# error foobar
#endif

#if (CHAR_BIT != 8)
# error foobar
#endif


int main()
{
// overflow on purpose, damn the warnings...
byte a = 255U + 0U;
byte b = 255U + 1U;
byte c = 255U + 2U;
byte d = 255U + 3U;

std::cout << "a = " << (unsigned long)a << std::endl;
std::cout << "b = " << (unsigned long)b << std::endl;
std::cout << "c = " << (unsigned long)c << std::endl;
std::cout << "c = " << (unsigned long)d << std::endl;
std::cout << "CHAR_BIT = " << (unsigned long)CHAR_BIT << std::endl;

return 0;
}
__________________

;^)


Sam

unread,
Apr 6, 2023, 7:00:05 PM4/6/23
to
Mut...@dastardlyhq.com writes:

> I really don't understand why std::string doesn't at least have a case
> insensitive equality/compare function. Is this asking too much in 2023?

Unfortunately, things have become quite complicated when all of these
strange alphabets came into existance, with all of those funny-looking
characters that use the same octets, depending on which part of the world
you happen to live in.

Daniel

unread,
Apr 6, 2023, 9:27:58 PM4/6/23
to
Indeed. If it ain't Yankee talk, it ain't talk.

Daniel

Bonita Montero

unread,
Apr 6, 2023, 11:11:47 PM4/6/23
to
Am 06.04.2023 um 17:10 schrieb Mut...@dastardlyhq.com:

> cout << strA << endl;

What about this:

#include <iostream>
#include <string>
#include <type_traits>

using namespace std;

template<typename Elem>
struct icase_traits : public char_traits<Elem>
{
using char_type = char_traits<Elem>::char_type;
private:
static constexpr Elem tolower( char_type c ) noexcept
{
return c < 'A' || c > 'Z' ? c : c - ('A' - 'a');
}
public:
static constexpr bool eq( char_type a, char_type b ) noexcept
{
return tolower( a ) == tolower( b );
}
static constexpr bool lt( char_type a, char_type b ) noexcept
{
return tolower( a ) < tolower( b );
}
static constexpr int compare( char_type const *s1, char_type const *s2,
size_t count )
{
for( size_t i = 0; i != count; ++i )
{
char_type
a = tolower( s1[i] ),
b = tolower( s2[i] );
if( a == b ) [[likely]]
continue;
if constexpr( is_integral_v<char_type> && sizeof(char_type) <
sizeof(int) )
return (int)a - (int)b;
else
return a < b ? -1 : 1;
}
return 0;
}
};

template<typename Elem, typename Alloc = allocator<Elem>>
using icase_string = basic_string<Elem, icase_traits<Elem>, Alloc>;

template<typename CharT, typename OTraits, typename Alloc>
basic_ostream<CharT, OTraits> &operator <<( basic_ostream<CharT,
OTraits> &str, icase_string<CharT, Alloc> const &out )
{
return str << out.c_str();
}

int main()
{
icase_string<char>
strA( "hello" ),
strB( "Hello" );
cout << (strA == strB) << endl;
cout << strA << endl;
}

Chris M. Thomasson

unread,
Apr 7, 2023, 2:29:47 AM4/7/23
to
______________
> #if (CHAR_BIT != 8)
> #   error foobar
> #endif
^^^^^^^^^^^^

this was already checked for up stream.

>
>
> int main()
> {
>     // overflow on purpose, damn the warnings...
>     byte a = 255U + 0U;
>     byte b = 255U + 1U;
>     byte c = 255U + 2U;
>     byte d = 255U + 3U;
>
>     std::cout << "a = " << (unsigned long)a << std::endl;
>     std::cout << "b = " << (unsigned long)b << std::endl;
>     std::cout << "c = " << (unsigned long)c << std::endl;

>     std::cout << "c = " << (unsigned long)d << std::endl;
^^^^^^^^^^^^^^^^^^^^^
Ummm... That should be

std::cout << "d = " << (unsigned long)d << std::endl;


>     std::cout << "CHAR_BIT = " << (unsigned long)CHAR_BIT << std::endl;
>
>     return 0;
> }
> __________________
>
> ;^)
>
>

Damn it! Sorry for that crap. That's what I get for typing code directly
in the damn newsreader. ;^o

Mut...@dastardlyhq.com

unread,
Apr 7, 2023, 5:05:41 AM4/7/23
to
On Fri, 7 Apr 2023 05:13:22 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>Am 06.04.2023 um 17:10 schrieb Mut...@dastardlyhq.com:
>
>> cout << strA << endl;
>
>What about this:

Sure. The point isn't whether it can be done, the point is I should be
able to inherit from std::string, overload a few operators and everything
Just Works.

Unfortunately it doesn't and sometimes I wonder if some of the people who
wrote the standard library have actually heard of inheritence.

Mut...@dastardlyhq.com

unread,
Apr 7, 2023, 12:10:35 PM4/7/23
to
Got a case insensitive string class working in the end for my purposes. Might
not be everything needed in other use cases however:

Class def is:

struct ci_string: public string
{
using string::string;

ci_string(const ci_string &cis): string(cis) { }
bool operator<(const ci_string &rhs) const;
bool operator==(const ci_string &rhs) const;
bool operator==(const char *rhs) const;
ci_string &operator=(const ci_string &rhs);
template<typename T> ci_string &operator+=(const T rhs)
{
string::operator=(string(c_str()) + rhs);
return *this;
}
};

ci_string operator+(const ci_string &lhs, const ci_string &rhs);
ci_string operator+(const ci_string &lhs, const char *rhs);

Implementation:

bool ci_string::operator<(const ci_string &rhs) const
{
return strcasecmp(c_str(),rhs.c_str()) < 0;
}


bool ci_string::operator==(const ci_string &rhs) const
{
return !strcasecmp(c_str(),rhs.c_str());
}


bool ci_string::operator==(const char *rhs) const
{
return !strcasecmp(c_str(),rhs);
}


ci_string &ci_string::operator=(const ci_string &rhs)
{
string::operator=(rhs.c_str());
return *this;
}


ci_string operator+(const ci_string &lhs, const ci_string &rhs)
{
return (string(lhs.c_str()) + rhs.c_str()).c_str();
}


ci_string operator+(const ci_string &lhs, const char *rhs)
{
return (string(lhs.c_str()) + rhs).c_str();
}


The copy constructor took a while to figure out thanks to the usual
unintelligable gibberish errors compilers fling out when dealing with STL
issues.

Anyway, hopefully it might be useful to someone.

Paavo Helde

unread,
Apr 7, 2023, 12:27:30 PM4/7/23
to
If you want a std::string which does not behave as a std::string, for
example in comparisons, then you cannot use public derivation, by
Liskov's principle. It's as simple as that.


Bonita Montero

unread,
Apr 7, 2023, 2:26:07 PM4/7/23
to
Am 07.04.2023 um 11:05 schrieb Mut...@dastardlyhq.com:

> Sure. The point isn't whether it can be done, the point is I should be
> able to inherit from std::string, overload a few operators and everything
> Just Works.

I think my solution is how the C++ standard should be extended.

red floyd

unread,
Apr 7, 2023, 10:51:00 PM4/7/23
to
On 4/6/2023 6:53 AM, Mut...@dastardlyhq.com wrote:
> On Thu, 6 Apr 2023 02:46:16 -0700 (PDT)
> Malcolm McLean <malcolm.ar...@gmail.com> wrote:
>> On Thursday, 6 April 2023 at 10:07:51 UTC+1, Mut...@dastardlyhq.com wrote:
>>> template<typename T> bool operator==(T rhs)
>>> {
>>> return !strcasecmp(c_str(),rhs);
>>> }
>>>
>>> That didn't work either.
>>>
>>> What have I done wrong? This is really starting to annoy me.
>>>
>> The convention is to supply the "less than" (<) operator.
>> Algorithms test for equality by testing that both permutations of "less than"
>> return false.
>
> I tried overloading <, >, ==, !=. Nothing worked.
>
> Overloading == worked for direct comparisons between ci_string objects but
> when comparing STL containers containing ci_string it didn't.
>
> Sometimes I can understand why some people just give up with C++ and use
> something else. It really shouldn't be this hard to do something this simple.
>

Technically it uses std::less, not "operator <". The Standard permits
specializing std::less for your type.


Bonita Montero

unread,
Apr 8, 2023, 9:47:53 AM4/8/23
to
Am 07.04.2023 um 05:13 schrieb Bonita Montero:

> template<typename CharT, typename OTraits, typename Alloc>
> basic_ostream<CharT, OTraits> &operator <<( basic_ostream<CharT,
> OTraits> &str, icase_string<CharT, Alloc> const &out )
> {
>     return str << out.c_str();
> }

Or better this:

template<typename CharT, typename StreamTraits, typename StringTraits,
typename Alloc>
basic_ostream<CharT, StreamTraits> &operator <<( basic_ostream<CharT,
StreamTraits> &str, basic_string<CharT, StringTraits, Alloc> const &out )
{
return str << basic_string_view<CharT, StreamTraits>( out.cbegin(),
out.cend() );
}

Bonita Montero

unread,
Apr 8, 2023, 11:13:05 AM4/8/23
to
Am 08.04.2023 um 15:49 schrieb Bonita Montero:

> Or better this:
>
> template<typename CharT, typename StreamTraits, typename StringTraits,
> typename Alloc>
> basic_ostream<CharT, StreamTraits> &operator <<( basic_ostream<CharT,
> StreamTraits> &str, basic_string<CharT, StringTraits, Alloc> const &out )
> {
>     return str << basic_string_view<CharT, StreamTraits>( out.cbegin(),
> out.cend() );
> }

Or how about that:

template<typename CharT, typename StreamTraits, typename Container>
requires random_access_iterator<decltype(Container().cbegin())> &&
same_as<iter_value_t<decltype(Container().cbegin())>, CharT>
&& random_access_iterator<decltype(Container().cend())> && same_as <
iter_value_t<decltype(Container().cend())>, CharT>
basic_ostream<CharT, StreamTraits>& operator <<( basic_ostream<CharT,
StreamTraits>& str, Container const &out )
{
return str << basic_string_view<CharT, StreamTraits>(out.cbegin(),
out.cend());
}

With that you could even print a vector
of chars without any additional effort.
0 new messages