views:

160

answers:

4

I'm trying to use the standard library's qsort to sort an array of wide characters:

wchar_t a = L'a';
wchar_t a1 = L'ä';
wchar_t b = L'z';
wchar_t chararray[] = {b, a, a1};  
length = wcslen(chararray);

qsort(chararray, length, sizeof(wchar_t), wcscoll);

Now I think the functions involved have these prototypes:

int wcscoll(const wchar_t *ws1, const wchar_t *ws2);
void qsort(void *base, size_t num, size_t size, int (*comp_func)(const void *, const void *))

The results are completely as expected, but why am I getting the compiler warning "passing argument 4 of ‘qsort’ from incompatible pointer type"? And how can I cast wcscoll to fit the prototype?

The warning goes away if I define and pass in a separate comparison function:

int widecharcomp(const void *arg1, const void *arg2)
{
    return wcscoll(arg1, arg2);
}

... but this one looks like it should have error handling for when the arguments are not of type wchar_t *.

+8  A: 

You've done pretty much the right way. The gcc documentation for strcoll and wcscoll gives an example similar to this as the correct way to use strcoll or wcscoll with qsort.

 /* This is the comparison function used with qsort. */

 int
 compare_elements (char **p1, char **p2)
 {
   return strcoll (*p1, *p2);
 }

 /* This is the entry point---the function to sort
    strings using the locale's collating sequence. */

 void
 sort_strings (char **array, int nstrings)
 {
   /* Sort temp_array by comparing the strings. */
   qsort (array, nstrings,
          sizeof (char *), compare_elements);
 }

This example actually does raise the warning that you want to get rid of, but again it can be gotten around by changing the char** to const void* in the arguments to compare_elements, and then explicitly casting to const char**.

You're right in observing that this is type-unsafe, but type safety is not exactly one of C's strong points. C doesn't have anything like generics or templates, so the only way that qsort can work on an arbitrary type is for its comparison function to accept void*s. It's up to the programmer to make sure that the comparison function is not used in a context where it may be passed arguments that are not the expected type.

That said, there is an error in your code. What the comparison function receives is not the elements to be compared, but rather pointers to the elements to be compared. So if the elements are strings, that means pointer-to-pointer. So when you write

return wcscoll(arg1, arg2);

You are actually passing wscoll a wchar_t** when it expects a wchar_t*. The correct way to do this, while suppressing the warning, would be:

int widecharcomp(const void *arg1, const void *arg2)
{
    return wcscoll(*(const w_char_t**)arg1, *(const w_char_t**)arg2);
}

as ugly as that is.

Edit:

Just took another look at the top bit of your code. Your error is really twofold here. You're trying to use wcscoll to sort characters. It's a function meant to sort strings (which in C are pointers to nul-terminated sequences of characters). The above was written assuming you were trying to sort strings. If you want to sort characters, then wcscoll is not the appropriate function to use, but everything above regarding qsort still applies.

Tyler McHenry
Good catch on the bug, however it looks like the OP wants to sort the characters within a string, in which case he's getting the correct pointers to the element to be sorted, but `wcscoll()` isn't the right function to call unless the chars are moved to local, one-char length, null terminated strings (in which case a wrapper function is pretty much mandatory). Is there a standard lib function for comparing single char/wchar_t values using a locale's collating sequence?
Michael Burr
Yes, I (she, not he, btw) wanted to start sorting single characters before sorting entire strings. I'm trying to get an handle on wchar_t.
chryss
Many thanks. I'm still working on digesting this, very helpful. The original error was to think wcscoll sorts characters.
chryss
A: 

You can't cast a function pointer to a different type, your current solution is as good it gets

Andreas Brinck
+2  A: 

You've coded up your solution already (however, see other answers and edits at the end of this one about with the choice of the comparison function you're using and the data being passed to qsort()).

You could drop the wrapper function by casting the function pointer you pass to qsort() to the appropriate type, but I think using a wrapper is a better solution from a maintainability perspective. If you really want to avoid a wrapper function (maybe you're running into a measurable running into perf issue), you can cast like so:

qsort(chararray, length, sizeof(wchar_t), (int(*)(const void*,const void*))wcscoll);

Or make it arguably more readable using a typedef for the compare function type:

typedef
int (*comp_func_t)(const void *, const void *);

/* ... */
qsort(chararray, length, sizeof(wchar_t), (comp_func_t) wcscoll);

Unfortunately, the straight C qsort() can't be typesafe, so it can't have have "error handling for when the arguments are not of type wchar_t". You, the programmer, are responsible for ensuring that you're passing the correct data, sizes and comparison function to qsort().


Edit:

To address some of the problems mentioned in other answers about the types being passed ot the compare function, here's a routine that can be used to sort wchar_t using the current locale's collating sequence. The library might have something better, but I'm not aware of it at the moment:

int wchar_t_coll( const void* p1, const void* p2)
{
    wchar_t s1[2] = {0};
    wchar_t s2[2] = {0};

    s1[0] = * (wchar_t*)p1;
    s2[0] = * (wchar_t*)p2;

    return wcscoll( s1, s2);
}

Also note, that the chararray you're passing to wcslen() isn't properly terminated - you'll need a 0 at the end of the initializer:

wchar_t chararray[] = {b, a, a1, 0};  
Michael Burr
Hello Michael; you wrote "You could drop the wrapper function by casting the function pointer you pass to qsort() to the appropriate type". This is strongly discouraged in numerous C FAQs, usually in the question "I am trying to pass the function `strcmp` to `qsort`". That answer is always to use a wrapper. The rationale probably is that the ABI may pass arguments differently according to subtle differences in their types. You can cast from a function pointer type to another, and cast back to the original type, but that's the only legal use of function pointer casts.
Pascal Cuoq
@Pascal: I agree. I should strengthen my answer's comment to that effect.
Michael Burr
+4  A: 

There are two problems: you've mixed up wchar_t and wchar_t*, and you've tried to pass off a wchar_t* as a void*.

First, you've told qsort to sort an array of wchar_t. But wcscoll doesn't compare wchar_t, it compares wide character strings which have the type wchar_t*. The fact that your comparison appears to have worked is due to your test data which just happens to work well under both interpretations.

If you wanted to sort characters, you need to call an appropriate function (I don't know the wide character API well enough to tell you which one). If you wanted to sort strings, you need to allocate an array of strings (of type wchar_t *).

Furthermore, even if you had an array of wchar_t*, you could not portably pass wcscoll as an argument to qsort. The issue is that there is no guarantee that wchar_t* and void* have the same representation. Some machines have word pointers that have a different representation from byte pointers; on such a machine, qsort would pass byte pointers to elements of the array to wcscoll, and this wouldn't work because wcscoll expects byte pointers. The solution is to write a trivial wrapper function that performs the conversion if necessary. A trivial wrapper is often necessary with qsort.

Gilles
This is a very succinct explanation. Many thanks. I'm going to mark this one as correct, as it will probably be more useful for future readers, even though I personally learnt more from Tyler McHenry's, who discusses the code in detail. So future readers take note: both are worth your time if you're running into the same problem.
chryss