Why does C++11 not support declaring extern "C" on a static member function?

xmllmx picture xmllmx · Jan 18, 2013 · Viewed 8.6k times · Source

Provided that I have a C library containing a function declared as void g(void (*callback)()); The following code is elegant yet illegal:

struct A
{
    // error C2159: more than one storage class specified (VC++ Nov 2012 CTP)
    static extern "C" void callback()
    {}
};

g(A::callback); 

Why does C++11 not support this?

Answer

Potatoswatter picture Potatoswatter · Jan 18, 2013

This is a particularly confusing topic to wade into. Let's attack §7.5 "Linkage specifications" [dcl.link].

1) All function types, function names with external linkage, and variable names with external linkage have a language linkage.

Note that the property of language linkage applies to two completely different kinds of entities: types and names.

A function has a generally-invisible bit of information in its type which identifies which ABI it conforms to: C calling conventions, Pascal, Fortran, all might be specified to use the stack in different ways, so calling them through a pointer requires knowing the invisible language-tag.

The name of a variable or function from another language can be accessed syntactically through C++, or from the other language referring to a C++ declaration. But not every language can match up with C++'s naming scheme and OO model. So interfaces in this scheme don't include classes.

Because these things are managed separately, it's possible to have something with different linkage in its type (calling conventions) and its name (linker symbol).

4) Linkage specifications nest. When linkage specifications nest, the innermost one determines the language linkage. A linkage specification does not establish a scope. A linkage-specification shall occur only in namespace scope (3.3). In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification. A C language linkage is ignored in determining the language linkage of the names of class members and the function type of class member functions.

The extern "C" {} affects all function declarations, including pointers and references, except member functions. Since a function may only be defined in a namespace or as a member, C functions can only be defined at namespace scope.

The standard gives an example here:

extern "C" typedef void FUNC_c();

class C {
   // the name of the function mf1 and the member 
   // function’s type have C++ language linkage; the 
   // parameter has type pointer to C function
   void mf1(FUNC_c*);

   // the name of the function mf2 and the member
   // function’s type have C++ language linkage
   FUNC_c mf2;

   // the name of the data member q has C++ language
   // linkage and the data member’s type is pointer to
   // C function
   static FUNC_c* q;
};

You can emulate the behavior you want using a typedef, though. From another example in §7.5/4,

extern "C" typedef void FUNC();

// the name f2 has C++ language linkage and the 
// function’s type has C language linkage
FUNC f2;

Combining these examples with yours, you can have

extern "C" typedef void callback_t();

callback_t A_callback; // declare function with C++ name and C type

struct A
{
    static callback_t &callback; // not a member function
};

// in source file:

// definition matches semantics of declaration, although not syntax
void A_callback() { ... }

// define static member reference
callback_t &A::callback = A_callback;

g(A::callback); // call syntax is emulated

In practice, it seldom makes a difference. C and C++ use compatible calling conventions on most platforms (see Jonathan Wakely's comments on this page for exceptions), as long as you don't try to pass or return a non-POD C++ class type. This is a less-implemented feature of C++, due to the confusing overloading of terms and conceptual distinctions ranging from subtle to academic.