1

C++20 added starts_with, ends_with to std::string.

Is there a nice way to get it to be case insensitive?

Note that perf matters so I do not want to lowercase/uppercase both strings(or std::min(len1, len2) parts of them).

Unlike regular <algorithm> algorithms starts_with has no overload with comparator so I see no nice way to do this.

And I kind of understand that 90+% of cases are case sensitive, and that member fns in C++ are avoided unless extremely useful... so I know why this limitation exists, I am just curious if something relatively readable can be hacked together in C++20 without me manually calling std::equal(or ranges version of equal) with custom comparator.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • [Does this answer your question?](https://stackoverflow.com/a/2886589/962089) It takes a bit of extra view/copy code to use it for just these two functions, but for the sake of avoiding copies, note that it does apply to `string_view` as well. However, tighter performance requirements might be better off with a SIMD implementation anyway, depending how optimized the compiler+standard library make that answer. – chris Sep 02 '20 at 19:13
  • @chris not really, I do not want case insensitive string class, I want case insensitive comparison of std::string – NoSenseEtAl Sep 02 '20 at 19:22
  • If case doesn't matter for you, why not store all your strings in a single case? – NathanOliver Sep 02 '20 at 19:22
  • 1
    @NoSenseEtAl, What I was getting at is that you can create a case-insensitive view over the case-sensitive string. – chris Sep 02 '20 at 19:23
  • @NathanOliver: there are reasons why it might be an unnecessary complication, for example user might name his file MyLog.log and he wants to see it displayed in the UI as MyLog.log even if we internally just check if the file case insensitively ends with .log (because that is what we allow). – NoSenseEtAl Sep 02 '20 at 19:45

3 Answers3

2

I was curious to see how the suggestion to apply this answer works out. This is the result.

Code from said answer:

struct ci_char_traits : public char_traits<char> {
    static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); }
    static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); }
    static bool lt(char c1, char c2) { return toupper(c1) <  toupper(c2); }
    static int compare(const char* s1, const char* s2, size_t n) {
        while( n-- != 0 ) {
            if( toupper(*s1) < toupper(*s2) ) return -1;
            if( toupper(*s1) > toupper(*s2) ) return 1;
            ++s1; ++s2;
        }
        return 0;
    }
    static const char* find(const char* s, int n, char a) {
        while( n-- > 0 && toupper(*s) != toupper(a) ) {
            ++s;
        }
        return s;
    }
};

typedef std::basic_string<char, ci_char_traits> ci_string;

There the answer suggested to use ci_string instead of std::string. Here we just want to create ci_views to std::strings:

typedef std::basic_string_view<char, ci_char_traits> ci_string_view;

int main()
{   
    std::string x{"ABCD"};
    std::string y{"abcd"};
    std::cout << ci_string_view{x.begin(),x.end()}.ends_with(ci_string_view{y.begin(),y.end()});
}

Output:

1

Live Example

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • did you compile this, I get: source>:33:12: error: no matching constructor for initialization of 'ci_string_view' (aka 'basic_string_view') return ci_string_view{value.begin(), value.end()}.ends_with(ci_string_view{ending.begin(), ending.end())); – NoSenseEtAl Sep 02 '20 at 19:42
  • @NoSenseEtAl https://godbolt.org/z/Ee86K8 its gcc 10.2, not sure about the current state of C++20 support, should be more or less complete by now – 463035818_is_not_an_ai Sep 02 '20 at 19:44
  • clang betrayed me :) https://godbolt.org/z/d58v6T Thank you for the link, I foolishly assumed that if any compiler compiles it then it is clang. :) – NoSenseEtAl Sep 02 '20 at 19:56
  • btw removing find seems to not break anything, do you know why it is needed, if it is needed? – NoSenseEtAl Sep 02 '20 at 20:01
  • @NoSenseEtAl sorry I cannot provide more than this bare bones answer. This was my first contact with both `basic_string` and `string_view` ;). I know what I'll have to read, maybe I will come back to this answer at some point to improve it – 463035818_is_not_an_ai Sep 02 '20 at 20:02
  • 1
    @NoSenseEtAl the comments under the original answer have some good suggestions, for example `int` should be `size_t`, though I just quoted as is – 463035818_is_not_an_ai Sep 02 '20 at 20:04
2

I also faced this problem recently.

If you don't mind using boost, there is istarts_with and iends_with functions.

They do exactly what you need.

e102_gamma
  • 31
  • 2
1

std::mismatch(s1.begin(), s1.end(), s2.begin(), s2.end(), <comparator>) will do what you want. You have to write the case-insensitve comparator, but I'm sure you can figure that out.

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
  • Nice, may not be obvious to future readers how to check the predicate(compare one of the return pair elements to .end(), but this should work nicely. – NoSenseEtAl Sep 13 '20 at 14:21