2

I came up with a bit of C++ code that I didn't really think too much about at first until I noticed that clang accepts it but gcc doesn't. And I think gcc is probably right about it the more I think about it. The problem is, I can't really think of a good, legal way to do what I want without a ton of boilerplate.

First, here is the code I wish would compile in g++:

extern "C" template <typename T>
using factory_pointer_t = T* (*)();

Since I think this might be an XY problem, a little background. I was working on a little plugin system with a fairly simple interface. Basically it only consists of a factory function that returns a pointer to an abstract base class. The abstract base class asks for methods to deal with memory management, similar to COM. Something like this:

class ITest {
public:
    virtual void release() = 0;
    virtual void test_method() = 0;
protected:
    virtual ~ITest(){}
};

Which would be implemented in the dynamic library something like this:

class Test : public ITest {
public:
    virtual void release() override {
        delete this;
    };
    virtual void test_method() override {
        std::cout << "Hello, shared World!\n";
    }
};

extern "C" ITest * testFactory() {
    return new Test{};
}

The research I have done on this suggest that this should work reliably across toolchains on at least windows and linux. Even if that turns out not to be the case, the system is intended for rapid prototyping on one toolchain with the opportunity to ditch the plugins and link everything statically somewhere down the line. No problems here so far.

My other research suggests that the extern "C" linkage specifier is very important for the dynamic linkage to work properly among all tools that support the same C ABI. The problem now is this:

I wanted to write some template code to help make some of this easier. But no combination of extern "C" together with the template syntax seems to work. My efforts to Google help have been thwarted by articles about the deprecated extern template. I wanted to give it the name of an abstract interface, and have it load the dll and resolve the symbol for the factory function. But as you can see, the type of the callbacks (factory_pointer_t<T>) are parameterized by the abstract interface pointer type they are returning. So my questions:

  1. Should this template using directive actually compile? (It does on clang but not gcc)

  2. If I declare my function pointer type as: extern "C" typedef void* (*factory_pointer)(); and cast the return value explicitly with a reinterpret_cast<>, will I be invoking undefined behavior? Given that in the DLL I would like to keep the function signature that I am exporting unchanged.

  3. Do I have any other options I might not have considered for accomplishing my stated goal that don't involve void*?

  4. Is a typedef with extern "C" for the function pointer strictly necessary? The standard can't seem to make up its mind. It says for example: in 7.5 of the 2014 draft standard: "Two function types with different language linkages are distinct types even if they are otherwise identical." but in 8.3.5 paragraph 8: "The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type."

I would really like to keep it working automatically in other compilers (i.e. I give it a type and tell it what library to load and I am done), but I guess I am not seeing what I need to do instead. Anything that involves more than a line of boilerplate per class, I would consider bad.

Since this is my first time experimenting with dynamic linking (more than letting the build system do it for me), I am eager to get some good advice.

In summary: I have a working solution based on the first line of code that only works in one compiler. I know it is probably wrong or at least not universally supported syntax. How do I do it the right way?

Tim Seguine
  • 2,887
  • 25
  • 38
  • I'm pretty sure `extern "C"` shouldn't affect function pointer types, so what effect is the specification on the alias in the first line supposed to have? – Sebastian Redl Jan 24 '15 at 20:33
  • @SebastianRedl I have read confilicting things about that. Some sources say `extern "C"` is a part of the type of the function pointer. If that is actually the case, then I would be casting to the wrong type. Might work (I suspect it could), but is it guaranteed to by the standard? – Tim Seguine Jan 24 '15 at 20:35
  • Easy enough to check. `extern "C" using fptr = void (*)(); void f(); static_assert(std::is_same::value, "extern C affects type");` – Sebastian Redl Jan 24 '15 at 20:37
  • @SebastianRedl `extern "C"` very much affects function types, and hence function pointer types. Quote: All function types, function names with external linkage, and variable names with external linkage have a *language linkage*. – n. m. could be an AI Jan 24 '15 at 20:39
  • 3
    @SebastianRedl You are checking what your compiler thinks, not what the standard says. – n. m. could be an AI Jan 24 '15 at 20:39
  • @SebastianRedl http://ideone.com/rYxv6Z ideone.com compiles it without errors, but I tend to agree with n.m. Better safe than sorry. – Tim Seguine Jan 24 '15 at 20:42
  • 3
    `extern "C"` is *definitely* a part of the type of the function pointer. Casting it to an "extern C++" function pointer is *definitely* not allowed by the standard. Major compilers *definitely* ignore this requirement of the standard. – n. m. could be an AI Jan 24 '15 at 20:48
  • I have added the "standards" tag, since it seems to be part of the discussion now. – Tim Seguine Jan 24 '15 at 20:53
  • 3
    If you are dealing with C++ code only, then you don't need anything to have C linkage. Why is C ABI important? If you want to mix and match compilers you cannot have so much as a class common between them, you must stick to C- compatible structs. – n. m. could be an AI Jan 24 '15 at 21:13
  • @n.m. it seems to be fairly common for C++ to use the C ABI for communicating across module boundaries. What I have read also suggests that abstract empty base classes behave fine as well as standard layout classes. I didn't invent the idiom I am describing here, I am just trying to make use of it. – Tim Seguine Jan 24 '15 at 21:17
  • @TimSeguine, what do you mean by "abstract empty base classes"? An abstract class has virtual functions, so such a type is not safe to use in C code, because the C code won't understand that the class contains a vptr – Jonathan Wakely Jan 24 '15 at 21:18
  • @JonathanWakely I am talking about interfacing c++ to c++. I'll see if I can find a link, but I read somewhere that for empty base classes with only pure virtual functions, the vtable layout is consistent. That could be wrong. I have no idea TBH. – Tim Seguine Jan 24 '15 at 21:20
  • 1
    Oh well then I also have to wonder why you're using C language linkage. The only good reason to use that across module boundaries is if you're compiling modules with two different, incompatible C++ compilers. – Jonathan Wakely Jan 24 '15 at 21:22
  • @JonathanWakely some other languages support the C ABI and the C++ vtable layout for ABCs (for example D) I guess I was just trying to keep my options open. If the best answer to this question turns out to be "don't use C linkage at all" then so be it. – Tim Seguine Jan 24 '15 at 21:27
  • 1
    @n.m.: From the question "this should work reliably across toolchains on at least windows and linux" -- that's where the C linkage requirement comes from. There is no cross-toolchain C++ ABI in Windows, except for vtable placement and layout (set by COM). – Ben Voigt Jan 24 '15 at 22:37
  • 1
    @JonathanWakely: Actually, on Windows C code can use C++ virtual functions (starting with a pointer to a type with no non-static data members). The ABI for this is controlled by COM. – Ben Voigt Jan 24 '15 at 22:39
  • @BenVoigt that's what I'm asking. Does the cross-toolchain requirement make any practical sense? When would it come into play? – n. m. could be an AI Jan 25 '15 at 03:26
  • @n.m.: Yes, it makes a LOT of sense. If you want to distribute a library, or a user-mode wrapper for your device driver for some custom hardware, or something like that, you don't want to dictate what compiler your users develop with. You want to publish DLLs that, like the Windows DLLs themselves, export pure C and/or COM API signatures and can be consumed by any toolchain, any language, with any compiler settings (the last requires explicitly specifying calling convention and structure packing in your public header file). – Ben Voigt Jan 25 '15 at 03:28
  • @BenVoigt That's exactly what I'm am saying. You need all your visible types have C interface. The example code does not. If you want COM, then language linkage of function types seemingly doesn't matter, as they are all COM-compatible. – n. m. could be an AI Jan 25 '15 at 03:58
  • @n.m.: What part of a `factory_function_t` signature is not a pure C interface? The parameter and return types are pure C, and the question is about how to get a C language linkage as well. Besides that, any object pointer type is usable in a pure C interface. C may be unable to usefully dereference that pointer if the pointee type isn't POD, but non-C++ languages certainly can treat the pointer as opaque and pass it back into the same library. – Ben Voigt Jan 25 '15 at 05:13
  • `extern template` is not deprecated; it's a new C++11 feature. Are you confusing it with `export`? – T.C. Jan 25 '15 at 05:31
  • @BenVoigt `ITest` is not a C type. You can of course instantiate the template with C types exclusively, but then what's the point? – n. m. could be an AI Jan 25 '15 at 05:58
  • @n.m.: `ITest` is part of the background, the "what if this is an XY problem", but not essential to the question. The question is about how to make a template for a C linkage function pointer. And there definitely are some values of `T` for which the signature of that function pointer is 100% C compatible. Also, like I said, `ITest*` is C compatible. C just can't look inside, without non-portable information (platform-specific ABI concerning vtables). – Ben Voigt Jan 25 '15 at 06:09
  • @BenVoigt `ITest*` may be a C type (if you change class to struct) but its utility is very limited if you can't dereference it and call its member functions. – n. m. could be an AI Jan 25 '15 at 06:11
  • @n.m.: No one ever dereferences 90% of the types used by the Win32 API: `HANDLE`, `HWND`, `HBITMAP`, etc. All you do is pass them back to the library that created them. And when you do have a pointer to a COM component, you can go one step further (access the method table). You still can't access data members directly. – Ben Voigt Jan 25 '15 at 06:42
  • @BenVoigt This particular class is a factory. A factory is meant to be used. If you have COM, the linkage of function types is irrelevant. – n. m. could be an AI Jan 25 '15 at 07:44
  • There is no factory class here only factory (allocator) free functions. – Ben Voigt Jan 25 '15 at 08:28
  • @n.m. I am only using free functions and virtual tables of an otherwise empty class. On Windows this has to work always otherwise the compiler can't support COM. On Linux I am not as certain, but what little I have found suggests that the vtables are also always the same on a class without any members. If all I'm accessing are free functions and vtable entries then I don't think I have an issue. I am going to look into the linux situation more thoroughly, but for now it works for me on the platform s I am interested in. – Tim Seguine Jan 25 '15 at 11:01
  • And I think having module ABI stability is more important for me on Windows. Recompiling everything including dependencies is a lot easier to accomplish on linux in my experience, so it is a tradeoff I am willing to make. – Tim Seguine Jan 25 '15 at 11:16

2 Answers2

2
  1. Should this template using directive actually compile? (It does on clang but not gcc)

I'm not sure. The standard says that function types, function names and variable names have a language linkage, but it doesn't say whether you can use an alias template to produce such function types.

EDG rejects it too, but Clang might be correct to allow it.

Edit: I failed to find http://open-std.org/JTC1/SC22/WG21/docs/cwg_closed.html#1463 which points out [temp]/4 says

A template, a template explicit specialization (14.7.3), and a class template partial specialization shall not have C linkage.

So technically Clang is wrong to accept it, but the Evolution Working Group are going to consider whether it should be allowed.

  1. If I declare my function pointer type as: extern "C" typedef void* (*factory_pointer)(); and cast the return value explicitly with a reinterpret_cast<>, will I be invoking undefined behavior?

(Edit: I originally said yes here, because I misread the question)

No, but a static_cast would be better than reinterpret_cast.

  1. Do I have any other options I might not have considered for accomplishing my stated goal that don't involve void*?

Get rid of all the C language linkage. I don't think it's necessary or useful for your situation.

  1. Is a typedef with extern "C" for the function pointer strictly necessary? The standard can't seem to make up its mind. It says for example: in 7.5 of the 2014 draft standard: "Two function types with different language linkages are distinct types even if they are otherwise identical." but in 8.3.5 paragraph 8: "The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type."

That doesn't say anything about language linkage, so doesn't contradict the very clear statement in 7.5 which says it is part of the type.

Also note:

[expr.call]/1: Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined (7.5).

That's undefined because it would not work if for example C language linkage implies a different calling convention to C++ language linkage.

But most compilers will do the right thing because they don't actually use different calling conventions for C and C++ functions, and most don't even implement the rule that the types of functions with C language linkage are different. See e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=2316

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I get what you were saying for part one before you edited it, which is why I figured it was illegal. But what I actually wanted, I think is reasonable. If as 7.5 implies, the language linkage is a part of a functions type, I should be able to make an alias of that type. I can do it with explicit typedefs. All I wanted to do is parameterize the typedef, essentially. If getting rid of C linkage is the best solution, then that is irrelevant though. – Tim Seguine Jan 24 '15 at 21:37
  • Yes, I realised my original first paragraph was explaining why a function template can't have C language linkage ... but you're not trying to do that. I agree that using an alias template to refer to a family of function types with C language linkage is reasonable. I'll ask the committee to clarify which compiler is correct. – Jonathan Wakely Jan 24 '15 at 21:40
  • Oh wait, I misread what you want to do with `reinterpret_cast`, I thought you were talking about casting the function type from an extern C++" function to an extern "C" one or vice versa... I'll edit the answer to part two – Jonathan Wakely Jan 24 '15 at 21:51
  • Templates shall not have C linkage... but the Standard says that "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." so class types and typedef names aren't affected and the rule is not violated. Only for function templates is there a potential rule violation. – Ben Voigt Jan 25 '15 at 05:16
  • (Admittedly that raises the question of why "class template partial specialization" is even mentioned in that rule. I claim that's useless verbiage, since linkage specifications never applied to such in the first place.) – Ben Voigt Jan 25 '15 at 05:23
1

You can definitely do this (credit to Potatoswatter for spotting this ability in his answer here):

extern "C" {
    template <typename T>
    class factory_pointer_def {
        typedef T* (*type)();
    };
}

which can be combined with

template <typename T>
using factory_pointer_t = typename factory_pointer_def<T>::type;

and it may also be possible to write

extern "C" {
    template <typename T>
    using factory_pointer_t = T* (*)();
}

and get the desired effect, although the Standard doesn't give that as an example. However, what the Standard actually says is

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.

and an alias-template is neither of those, so the C language linkage will not be ignored.

So all you need to change is to use the block form of extern "C".


As Jonathan points out, giving a template C language linkage is illegal. But

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.

The language linkage doesn't apply to the template class nor template-alias. Only to the function type which is within the function pointer declaration.

Therefore, any of the above code using templates inside extern "C" blocks is perfectly legal.

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks. Your first example is almost something that I already tried. I left off the braces. I'll give it a try. Your reasoning seems sound in any case. – Tim Seguine Jan 25 '15 at 10:42
  • 1
    gcc still complains that it is a template with C linkage. – Tim Seguine Jan 25 '15 at 13:15
  • Both GCC and EDG treat your "perfectly legal" code as an attempt to declare a template with C linkage. I don't think your interpretation matches the C++ committee's, who agree that [temp]/4 makes the code ill-formed (otherwise DR 1463 would be closed as NAD not forwarded to EWG as a possible extension) – Jonathan Wakely Jan 25 '15 at 15:59
  • See http://open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#13 as well. If you were correct that you can "definitely do this" then that issue would not have been reported, or would have been closed because it's already possible today. This is an [active topic](http://cplusplus.github.io/EWG/ewg-active.html#106) and it's not true that you can "definitely do this" and have it work (except with Clang, which appears to support it as a non-standard extension) – Jonathan Wakely Jan 25 '15 at 16:04
  • @JonathanWakely: `clang` too... says "templates must have C++ linkage". But the template *does* have C++ language linkage, because "the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage". If the compiler applies it to the class type or type name, it is doing it wrong. Class types don't even have language linkage. "All function types, function names with external linkage, and variable names with external linkage have a language linkage." – Ben Voigt Jan 25 '15 at 16:05
  • @JonathanWakely: And the error message definitely is wrong; the Standard doesn't say that templates must have C++ linkage, it says they can't have C linkage... which is trivially met by a class template that has no language linkage at all. – Ben Voigt Jan 25 '15 at 16:06