It's unfair: I don't cast, but get a 'bad cast'

The following program throws a bad_cast during sorting of a vector of strings, but I am not aware that I am using a cast. For the comparision, I am using a function object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <algorithm>
#include <iostream>
#include <locale>
#include <string>
#include <vector>
using namespace std;

struct CompFunc {
    // Do case-insensitive string comparision
    inline bool operator()(string s1, string s2) {
        locale loc;
        return tolower(s1,loc) < tolower(s2,loc);
    }
} compfunc;

void do_things(vector<string> const& inp) {
    vector<string> wv(inp);
    cout << "SORTING" << endl;
    sort(wv.begin(),wv.end(),compfunc); // Throws a bad cast
    cout << "SORTED" << endl;
    // Rest of the code removed because not relevant to the problem
}

int main() {
    vector<string> v;
    v.push_back("A"); v.push_back("B"); v.push_back("a");
    do_things(v);
    return 0;
}


This is my compiler version:
i686-apple-darwin8-g++-4.0.1 (GCC) 4.0.1 (Apple Computer, Inc. build 5370)
There is certainly a bad cast here: from string to char one. Check line 12.
How do you expect to sort a const vector?
He copies it and sorts a copy.
Last edited on
Ah, I missed the copy. Why not take the parameter by copy then?
Why doesn't this give a compile time error? Is because you can construct a string from a char like this std::string{'c'};? But then why is it using dynamic_cast?
Honestly, I can't even find what overload of tolower is getting used here. I have no idea how the compiler is interpreting line 12.

EDIT: Ah, found it (no help to search functionality):
http://www.cplusplus.com/reference/locale/tolower/
http://en.cppreference.com/w/cpp/locale/tolower
Since it's a template, it accepts std::string at compile time.
Last edited on
It uses tolower overload from <locale>. It uses facets which does some runtime checks on input types and throws bad_cast if they can't makes sense of input type.
@OP: see http://stackoverflow.com/a/313990/1959975
Also consider using lambdas ;)
His problem is that he is trying to use tolower on strings. Which is not going to work no matter what overload he will use.

Solution is to either store char instead of strings, or compare first characters of strings (or use transform to change whole strings and compare strings if he want that)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct CompFunc {
    // Do case-insensitive string comparision
    inline bool operator()(string s1, string s2) {
        static locale loc; // *** static added
        return tolower(s1,loc) < tolower(s2,loc);
    }

    //////////////  added /////////////////////////
    // return string converted to all lower case using the ctype facet in locale
    static std::string tolower( std::string s, const std::locale& loc )
    {
        for( std::size_t i = 0 ; i < s.size() ; ++i ) s[i] = std::tolower( s[i], loc ) ;
        return s ;
    }
    ///////////////////////////////////////////////
} compfunc;




> Why doesn't this give a compile time error?
> But then why is it using dynamic_cast?

template< typename CHAR_TYPE > CHAR_TYPE std::tolower( CHAR_TYPE c, const locale& loc );
is instantiated with CHAR_TYPE == std::string.

The instantiation invokes std::use_facet< std::ctype<std::string> >(loc)
which throws std::bad_cast because std::has_facet< std::ctype<std::string> >(loc) is false.
(This IS specifies that std::bad_cast must be thrown in this case.)
Thanks to all. I don't know what I had in mind when thinking that the template version of std::tolower would for sure have a specialization for std::string! This idea sounded so obvious to me, that I didn't bother to check the standard. Oh my, I did too much programming in Python, Ruby and Perl lately...

Thank you for the good explanations. I'm surprised that the C++ standard doesn't provide a library function for this common task. I will go with the solution by JLBorges.
I'm surprised that the C++ standard doesn't provide a library function for this common task
std::transform + tolower and lambdas or std::bind.
I believe Boost has all kinds of string comparsion including case insensitive one.
rovf wrote:
I'm surprised that the C++ standard doesn't provide a library function for this common task.

It is because it is actually some advanced capabilities, with a fairly large data add-in to your program. Many libraries already exist to do this (ICU, for example).

The problem is that converting string case is not a trivial task.
Topic archived. No new replies allowed.