Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++ language courses for corporations. He is the author of All On C, C for COBOL Programmers, and UNIX for MS-DOS Users, and was a member of the ANSI C committee. He also does custom C/C++ programming and provides SystemArchitectonicssm services. He is president of the Independent Computer Consultants Association. His address is 4201 University Dr., Suite 102, Durham, NC 27707, You may fax questions for Ken to (919) 489-5239. Ken also receives email at kpugh@allen.com (Internet) and on Compuserve 70125,1142.
Q
In your column in the May 1994 issue of CUJ (which, by the way, arrived here a week or so after the July issue apparently the boat carrying May's issue to the Antipodes took a lengthy detour), you discuss a problem in using the qsort function to sort data within a C++ class. You describe a Catch-22 situation, in which the comparison function passed to qsort must be a member function if it is to access private data members of the class, but qsort is unable to call a C++ function because its calling convention differs from that of a C function.
There is a way to circumvent this problem, which works with all the C++ compilers I have tested (Borland C/C++ 3.1, Microsoft C/C++ 7.0, and GNU g++ 1.39.1). The trick is to declare the comparison function to be a static member function (See Listing 1) . Static member functions don't receive a this argument and (at least in these compilers) use the same calling conventions as ordinary C functions. Unfortunately, however, I suspect there is no guarantee that this technique will work with all C++ compilers. Does the proposed C++ standard have anything to say about this? Perhaps the standards committee might consider requiring static member functions to use the same calling conventions as C functions, as it would facilitate use of the standard qsort and bsearch C library functions.
By the way, the program listing which accompanied this discussion in the May issue contained at least two errors, and also raised a couple of questions in my mind. First the (minor) errors: 1) The declaration of class A lacks a terminating semicolon. 2) The use of compare_function in the sort must be preceded by a prototype. I have some more questions on Listing 1 (reproduced here see Listing 1)
1) Should the declarations of first and second in compare_function be of const DataType * or const A::DataType *? My guess would have been the latter, as DataType is a private member of class A, and really shouldn't be "visible" to an external function.
2) When the latter form is used, the Borland compiler compiles it without error; the Microsoft compiler fails with the error message: "'DataType' : cannot access 'private' member declared in class 'A'." Which is the correct behavior?
Listing 2 is a program which demonstrates my approach of declaring the comparison function to be a static member function.
I do enjoy reading your columns. Keep up the good work!
Cheers,
Eric Zurcher
Canberra, AustraliaA
For the purpose of implementation hiding, a static function would be better than a friend function, if it were guaranteed to work on all compilers. For the compilers you listed, static C++ functions do appear to have the same calling sequence as C functions. Other readers have responded with similar statements about these compilers. I do not believe this feature is in the standard.
Thank you for the sharp-eyed corrections to my program. My grammar checker does not look for a terminating ';'. In response to your first question, your comparison function should use:
const A::DataType *first = (A::DataType*) a; const A::DataType *second = (A::DataType*) b;This is explained in detail on pages 186-187 of The Annotated C++ Reference Manual (ARM) by Ellis and Stroustrop [1]. For those who do not have that reference handy, I'll paraphrase the ARM's explanation. The rules on nested classes have flip-flopped in recent years.Class names (as well as structure names) now follow the same rules of scoping as other names. In C, structures did not have scoping rules and therefore you could use these declarations:
const DataType *first = (DataType*) a; const DataType *second = (DataType*) b;The declaration of a structure template inside another structure template was just a convenience and had no scope meaning.In C++, using a nested class in a non-class function puts it in the scope of that class. A program requires scoping information if a reference appears outside of that class (i.e., its template and its member functions). This scope applies to all nested types. For example, you commonly see:
class A { public: enum Enumeration {One, Two, Three}; //... };and a non-member function as:
void non_member_function() { A:Enumeration a; //. . . };Access is a separate issue from scope. In answer to your second question, access control applies to both members (function and data) and nested types (ARM, p. 239). Therefore the attempt to access Datatype by the comparison function should not compile.Your second program is a bit cleaner on the access side. It eliminates the access problems of the first example, at the expense of a slightly more complex looking class interface. I will be glad when the new namespace mechanism becomes commonplace. Then we will have the same access protection as nested classes, but without the visual clutter.
Reference
[1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference Manual (Addison-Wesley, 1994).