简体   繁体   中英

is there a way to use the forwarding of std::less<> and a custom compare at the same time with std::set/std::map?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM