If I declare std::set<std::string>
I get case-sensitive comparison. If I want case-insensitive, I can write my own compare and declare like std::set<std::string,cmpi>
and that works fine too.
struct cmpi {
bool operator() (const std::string& a, const std::string& b) const {
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
};
However, in high-use cases, I'm searching the std::set
with a const char *
, eg find("string")
or find(pszVar)
and unfortunately a temporary std::string
(strlen, alloc, free) is created for the comparison operations. I can avoid all that by using std::set<std::string,std::less<>>
which forwards the type as-is with no temporary and works fine for case-sensitive compare. But what about a case-insensitive compare version of that?
In this example I'm creating a class to ensure things are happening as expected:
#include <iostream>
#include <set>
#include <string>
#include <string.h>
class Str : public std::string {
public:
Str(const char *s) : std::string(s) {
std::cout << "create" << std::endl;
}
~Str() {
std::cout << "destroy" << std::endl;
}
bool operator<(const Str &s) const {
std::cout << "local comparing Str:" << *this << " to Str:" << s << std::endl;
return strcasecmp( c_str(), s.c_str() ) < 0;
}
bool operator<(const char *s) const {
std::cout << "local comparing Str:" << *this << " to char*:" << s << std::endl;
return strcasecmp( c_str(), s ) < 0;
}
};
int main(void) {
std::set<Str,std::less<>> list;
list.emplace("A");
list.emplace("D");
list.emplace("C");
list.emplace("b");
for ( const auto &s : list ) {
std::cout << s << std::endl;
}
if ( auto s = list.find("c"); s != list.end() ) {
std::cout << "found c!" << std::endl;
}
else {
std::cout << "c not found" << std::endl;
}
return 0;
}
4 creates for the emplaces, output order is AbCD
, and no temporary create for the successful find.
So, is there a way to have the best of both worlds without having to declare two object types... a custom comparison that also forwards the comparison type? I Tried a few variations of things, eg a namespace surrounding a templated std::less, named struct surrounding the operators so I could have two sets, etc. without success.
To reiterate: I don't want one or the other comparison type... I want to have both:
std::set<Str,std::less<>> list_no_case;
std::set<Str,????> list_case;
I can avoid all that by using
std::set<std::string,std::less<>>
No, you can avoid all of that by using a comparison function that does asymmetric comparisons, of which std::less<>
is one example (and it only works because std::string
has a <
comparison operator with char const*
). You can just write your own in cmpi
. Just add additional operator()
overloads to do comparisons between std::string
and char const*
.
using is_transparent = int;
bool operator() (const std::string& a, const std::string& b) const
{
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
bool operator() (char const *a, const std::string& b) const
{
return strcasecmp(a, b.c_str()) < 0;
}
bool operator() (const std::string& a, char const *b) const
{
return strcasecmp(a.c_str(), b) < 0;
}
Note that the using is_transparent
part is needed to make this work. It's what a comparison uses to signal to the container that it allows asymmetric comparisons.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.