Another C vs. C++ incompatibility rant:
Void parameter lists

Ugh. C++ always finds another way to irk me.

I finally upgraded one of my systems to Snow Leopard and am trying out all the new compilers (gcc 4.2, llvm/gcc, clang). In addition, Apple sent me back a bug report telling me they fixed a problem with OpenAL buffer unqueuing returning the wrong buffer id, and wanted me to test it in Snow Leopard. 

Being OpenAL, I could have submitted a plain C example, but I wanted to use some standard data structures so Apple couldn't accuse my code of being buggy. I wanted a GUI too to help trigger the problem, so I obviously used Cocoa/Obj-C to build the UI. But from past experience, I knew the Core Audio team likes C++ and I really wanted them to fix my bug, so I decided to use STL data structures instead of Cocoa data structures hoping the code might look more inviting to them.

So I dug up my old OpenAL test program and tried to recompile it in Snow Leopard. 


Compile failed.

C++ Stupidity


The new default compiler, gcc 4.2 barfed in the OpenAL headers. Not my code, but OpenAL headers (as in, industry-standard, cross-platform headers).


Here is one of the different lines gcc trips up on (in OpenAL's alc.h):

ALC_API ALCcontext * ALC_APIENTRY alcGetCurrentContext( ALCvoid );


I get the *extremely helpful* error messages:

/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/OpenAL.framework/Headers/alc.h:182:0 /Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/OpenAL.framework/Headers/alc.h:182: error: '<anonymous>' has incomplete type

/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/OpenAL.framework/Headers/alc.h:182:0 /Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/OpenAL.framework/Headers/alc.h:182: error: invalid use of 'ALCvoid'


If you were wondering, ALCvoid is just a typedef to void.

typedef void ALCvoid;


So what the heck is the compiler complaining about? This worked fine in gcc 4.0 when I originally submitted the bug. This is all perfectly valid, standards compliant C code.


After doing quite a few searches, it appears I am not the first to encounter this problem. Other people using C++ and newer versions of gcc on various platforms have encountered the same thing.


The best summary I found was in this discussion thread (captured from comp.std.c++).

http://www.archivum.info/comp.std.c++/2006-04/00211/Re:_Defect_Report:_Definition_of_void_parameter_list_differs_from_C


In a nutshell, C++ forbids using a typedef here (ALCvoid) for a void parameter list, whereas this is perfectly fine in C. My initial knee-jerk reaction is, "Come on, this is stupid!" Then I remind myself how complex C+++ is and implementing C++ is and come to the conclusion, "Come on, this is really stupid!"


Fortunately for me, it looks like somebody caught this problem before Snow Leopard shipped, so if I use the 10.6 SDK instead of the 10.5 SDK, the header file has been altered to accommodate C++ and gcc 4.2. Basically, they replaced all the instances of ALCvoid with plain old void when in a empty parameter list situation. Simple and effective, but tragic and pathetic that C++ made it come to this. This also means you can't use the newer versions of the compilers with the compatibility SDKs (when using C++).


The Details


I'm going to guess most of you don't know the rules about function prototypes and void parameter lists in C and C++. I didn't know the subtle details of this one myself until several years ago. I had played with the gcc compiler warning flag -Wmissing-prototypes and sometimes got warnings in my pure C code, but for many years, I didn't understand why I got warnings in very limited circumstances.


Here's the crux of the situation. In C, a function that has no parameters must explicitly put void in the parameter list for the prototype.

int foo(void);


For historical reasons, the following prototype does not mean the same thing in C.

int foo();


What this latter prototype means in C is that you have a function called foo, but it says nothing about the arguments. So you could implement foo like this:


int foo(int a, int b)
{
return a+b;
}


Even though the prototype has no arguments, you can see the actual definition has two. As I mentioned, this is a historical fact from C before it was standardized (ANSI C89). Prior to standardization, the definition might have looked like:

foo(a, b)
int a, b;
{
return a+b;
}

(I think this is K&R notation.)

I speculate because the parameter types and function name were separated in the definition, this is why the function prototype omitted the parameters.


Anyway, this has all been deprecated since standardization (i.e. 20 years), but it also shows how hard it is to actually remove a language feature. (And an aside, it's been 10 years since C99 was ratified and Microsoft still refuses to support C99 in their compilers after all this time.) So proper C requires explicit parameters in the prototype, even if there are none. Interestingly, the definition does not. So this is modern correct C:

int foo(void);

int foo()
{
return 0;
}

C++ is not a superset of C

Now in C++, they changed things. For one, they don't require an explicit parameter list in the function prototype. So 

int foo(void);

and 

int foo();

mean the same thing in C++.


So this is all fine and dandy. But why does C++ flat out reject a typedef to void in the empty parameter list case? e.g.

ALenum alGetError(ALvoid);


And yet C++ is fine with a typedef to void in non-empty parameter list cases, e.g.

AL_API void AL_APIENTRY alBufferData( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq );


This is asinine. I've been using C++ for far too long that stuff like this should come as a surprise to me. (It's actually the first language I learned, though it may not be obvious from how much I talk about C, Obj-C, and Lua on this site.) But yet, C++ still manages to irk me like this. <sigh>


Maybe since C++0x isn't going to make ratification, this can still be fixed for C++0xA or whatever they end up calling it. Unfortunately, I don't even know how or who to complain to. Chances are the C++ committee will never know (let alone care) about this issue. (It looks like the people from comp.std.c++ already forwarded the information, but this was over 3 years ago and the gcc breakage is new so I wouldn't be surprised to learn that C++ is going to continue to do this stupid thing.)


In conclusion, do me a favor and smack the next person you see who incorrectly claims C++ is a superset of C. (Or if you meet a C++ standards committee person, just smack them.)


Some Reference Links

Incompatibilities Between ISO C and ISO C++

C and C++: Case Studies in Compatibility



Copyright © PlayControl Software, LLC / Eric Wing