1

Following code works well without issues but wondering, if it is possible to rewrite custom comparison operator using boost::iequals , which compares without converting to upper.

std::string copyToUpper(std::string s)
{
    std::transform(s.begin(),s.end(),s.begin(),::toupper);
    return s;   
}

struct caseInsensitiveCompare {
    bool operator() (const std::string& a, const std::string& b) const {
        return (copyToUpper(a).compare(copyToUpper(b)) < 0);
    }
};
std::set<std::string, caseInsensitiveCompare> keys;
nsivakr
  • 1,565
  • 2
  • 25
  • 46

2 Answers2

3

I would define a comparator like this:

#include <boost/algorithm/string/predicate.hpp>

struct ci_compare {
    bool operator()(std::string_view const& a,
                    std::string_view const& b) const {
        return boost::ilexicographical_compare(a, b);
    }
};

Then you can use it with your datastructure of choice:

std::set<std::string, ci_compare> keys;

Live On Compiler Explorer

#include <boost/algorithm/string/predicate.hpp>

struct ci_compare {
    bool operator()(std::string_view const& a,
                    std::string_view const& b) const {
        return boost::ilexicographical_compare(a, b);
    }
};

#include <boost/core/demangle.hpp>
#include <fmt/ranges.h>
#include <map>
#include <set>
using namespace std::string_literals;

auto dump(auto const& data) {
    fmt::print("---\nData structure: {}\nData: {}\nContains 'three'? {}\n",
            boost::core::demangle(typeid(data).name()), data,
            data.contains("three"));
}

int main() {
    dump(std::set{{"hellO", "hEllo", "world"}, ci_compare{}});

    dump(std::map<std::string, int, ci_compare>{
        std::pair{"one"s, 1}, {"TWO"s, 2}, {"Three", 3}});
}

Prints

---
Data structure: std::set<char const*, ci_compare, std::allocator<char const*> >
Data: {"hellO", "world"}
Contains 'three'? false
---
Data structure: std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, ci_compare, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int> > >
Data: {("one", 1), ("Three", 3), ("TWO", 2)}
Contains 'three'? true
sehe
  • 374,641
  • 47
  • 450
  • 633
1

Almost all STL containers rely on strict weak ordering. So the comparison function needs to return, not whether the strings are equal to each other, but that one is "less" than the other. But boost::iequals checks for equality and not if one string is "less" than the other, so you can't use it for the comparator of a map, a set or a sort function.

Jerry Jeremiah
  • 9,045
  • 2
  • 23
  • 32
  • @nsivakr You might want to look into [`boost::algorithm::is_iless`](https://www.boost.org/doc/libs/release/doc/html/boost/algorithm/is_iless.html) - I was trying to write an answer about that but I can't get it to work properly myself. I'm getting a `std::bad_cast` when inserting elements into the `std::set`. Annoying ... :-) – Ted Lyngmo Mar 03 '22 at 02:59
  • 1
    Thanks @TedLyngmo. Will look into it. – nsivakr Mar 03 '22 at 03:01
  • @TedLyngmo that's the hallmark of missing locale facets – sehe Mar 03 '22 at 16:52
  • @sehe Oh, cool. I'll give it another try later. Thanks! – Ted Lyngmo Mar 03 '22 at 17:01