Overload resolution with extern “C” linkage

2020-08-11 11:02发布

问题:

In a mixed C/C++ project, we need to make a call from C to a C++ function. The function to be called is overloaded as three separate functions, but we can ignore that from the C-side, we just pick the one most suitable and stick to that one.

There's two ways to do this: (1) write a small C++ wrapper with a extern "C" function that forwards the call to the chosen overloaded function, or (2) the hackish way to just declare the one function we want to call from C as extern "C".

The question is, is there any disadvantages (apart from nightmares and bad karma) to go for the second variant? In other words, given three overloaded function, where one is declared as exern "C", should we expect trouble with the C++ side, or is this well defined according to the standard?

回答1:

I believe the language in the standard is specifically written to allow exactly one function with "C" linkage, and an arbitrary number of other functions with "C++" linkage that overload the same name (§[dcl.link]/6):

At most one function with a particular name can have C language linkage. Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function. Two declarations for an object with C language linkage with the same name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same object.

The standard shows the following example:

complex sqrt(complex); // C + + linkage by default
extern "C" {
    double sqrt(double); // C linkage
}


回答2:

As long as you follow the other rules for extern-C functions (such as their special name requirements), specifying one of the overloads as extern-C is fine according to the standard. If you happen to use function pointers to these functions, be aware that language linkage is part of the function type, and needing a function pointer to this function may decide the issue for you.

Otherwise, I don't see any significant disadvantages. Even the potential disadvantage of copying parameters and return value can be mitigated by compiler- and implementation-specifics that allow you to inline the function – if that is determined to be a problem.

namespace your_project {  // You do use one, right? :)
  void f(int x);
  void f(char x);
  void f(other_overloads x);
}

extern "C"
void f(int x) {
  your_project::f(x);
}


回答3:

Even if it was allowed by the standard, future maintainers of the code will probably be extremely confused and might even remove the extern "C", breaking the C code (possibly far enough later that the events aren't linkable).

Just write the wrapper.

EDIT: From C++03 7.5/5:

If two declarations of the same function or object specify different linkage specifications (that is, the linkage specifications of these declarations specify different string literals), the program is ill-formed if the declarations appear in the same translation unit, and the one definition rule (3.2) applies if the declarations appear in different translation units...

I interpret this to not apply since C and C++ functions with the same name aren't actually the same function but this interpretation may be wrong.

Then from C++03 7.5/6:

At most one function with a particular name can have C language linkage...

This then implies that you could have other, non-C-linkage, functions with the same name. In this case, C++ overloads.



回答4:

(This answer applies to C++14; other answers so far are C++03).

It is permitted to use overloading. If there is an extern "C" function definition of some particular name then the following conditions apply (references to C++14 in brackets):

  • The declaration of the extern "C" function must be visible at the point of any declaration or definition of overloads of that function name (7.5/5)
  • There must be no other extern "C" definition of a function or variable with the same name, anywhere. (7.5/6)
  • An overloaded function with the same name must not be declared at global scope. (7.5/6)
  • Within the same namespace as the extern "C" function, there must not be another function declaration with the same name and parameter list. (7.5/5)

If any violation of the above rules occurs in the same translation unit the compiler must diagnose it; otherwise it is undefined behaviour with no diagnostic required.

So your header file might look something like:

namespace foo
{
    extern "C" void bar();
    void bar(int);
    void bar(std::string);
}

The last bullet point says that you cannot overload solely on linkage; this is ill-formed:

namespace foo
{
    extern "C" void bar();
    void bar();               // error
}

However you can do this at different namespaces:

extern "C" void bar();
namespace foo
{
    void bar();
}

in which case , normal rules of unqualified lookup determine whether a call bar() in some code finds ::bar, foo::bar, or ambiguous.