[cfe-users] clang and C++: exporting member function template from library using attribute visibility("default")

Richard Smith via cfe-users cfe-users at lists.llvm.org
Sun Apr 5 23:00:29 PDT 2020


On Mon, 30 Mar 2020 at 14:19, Alexis Murzeau via cfe-users <
cfe-users at lists.llvm.org> wrote:

> Hi,
>
> When using clang, I discovered that it errors out where other compilers
> doesn't (GCC and MSVC).
>
> I'm trying to do this:
>  - Have a library compiled with -fvisibility=hidden and adding
> __attribute__((visibility("default")))
>    only for stuff that must be exported from the library .so.
>
>  - Have an exported class (with __attribute__((visibility("default"))))
> that
>    declares a member function template
>
>  - A cpp file in the library does does instantiation of that member
> function
>    template for all applicable types that would ever be usable by that
> member function template.
>

If you want one source file to provide instantiations for use by a
different source file, it's not sufficient to merely trigger those
instantiations. That only generates a discardable definition of the
instantiation, and if (for example) that definition is inlined, no symbol
will be provided for other translation units to link against. Instead you
need to use an explicit instantiation. (See
https://en.cppreference.com/w/cpp/language/function_template#Explicit_instantiation
)


> When compiling that library, the member function template instantiations
> are
> not being exported from the library (they are hidden).
>
> The error comes then when an executable try to use that function, but
> compilation fails because of undefined reference to `void
> NetworkPacketLogger::logType<T1>(T1 const*)'.
>
>
> In a lib.h:
> ```
> // An enum defining the network packet
> enum class Opcode {
>         T1,
>         T2
> };
>
> // Base class
> struct NetworkPacket {
>         NetworkPacket(enum Opcode t) : type(t) {}
>
>         enum Opcode type;
> };
>
> // Sample of possible network packets
> struct T1 : public NetworkPacket {
>         T1() : NetworkPacket(Opcode::T1) {}
>         int value;
> };
>
> struct T2 : public NetworkPacket {
>         T2() : NetworkPacket(Opcode::T2) {}
>         float value;
> };
>
> // Class to log a network packet
> class __attribute__((visibility("default"))) NetworkPacketLogger {
> public:
>         // "Slow" function that find the correct packet opcode and log
> it's content
>         // vvv this function is exported just because of the attribute on
> the NetworkPacketLogger, OK
>         static void logAbstractType(struct NetworkPacket* abstractData);
>
>         // Fast function that doesn't have to find the packet opcode
>         // It does just a log of data->value
>         // T can only be either T1 or T2
>
>         // vvv this is the hidden function that should be exported from
> the lib
>         template<class T> static void logType(const T* data)
> __attribute__((visibility("default")));
> };
> ```
>
> In a lib.cpp:
> ```
> template<class T>
> void NetworkPacketLogger::logType(const T* data) {
>         std::cout << data->value;
> }
>
> void NetworkPacketLogger::logAbstractType(struct NetworkPacket*
> abstractData) {
>         // Possibly generated code is there are many possible network
> packets
>         // This will implicitely instanciate all possible combination of
> NetworkPacketLogger::logType<T>
>         // I want these instanciations to be available by user of the
> library
>

To make these available to users, you should explicitly instantiate them as
follows:

template void NetworkPacketLogger::logType(const T1*);
template void NetworkPacketLogger::logType(const T2*);

        switch(abstractData->type) {
>                 case Opcode::T1:
>                         logType(static_cast<T1*>(abstractData));
>                         break;
>                 case Opcode::T2:
>                         logType(static_cast<T2*>(abstractData));
>                         break;
>         }
> }
> ```
>
>
> I found by tweaking the code that, if I add a
> __attribute__((visibility("default")))
> on struct T1 and struct T2, then logAbstractType<T1> and
> logAbstractType<T2> are exported.
>
> But why is this required for clang ?
> It seems to be like this old bug:
> https://bugs.llvm.org/show_bug.cgi?id=8457
>
> Is this expected ?
>

Yes. You're probably just getting lucky with the other compilers, and they
happen to not inline either of the 'logType' functions.


> Shouldn't a warning be emitted when a function that should be
> "visibility("default")"
> is not because of one of the arguments use a struct with
> visibility("hidden") ?
>
>
> I'm attaching a test case.
> Can be compiled with something like this:
> ```
> mkdir build && cd build
> CC=clang-9 CXX=clang++-9 cmake ..
> make
> nm -CD liblib.so  | grep logType
> ```
>
> nm will print only this (no NetworkPacketLogger::logType<T1>):
> 00000000000011b0 T NetworkPacketLogger::logAbstractType(NetworkPacket*)
> 0000000000001260 W void NetworkPacketLogger::logType<T2>(T2 const*)
> 0000000000001290 W void NetworkPacketLogger::logType<T3>(T3 const*)
>
>
> Thanks for your hindsight :)
>
> --
> Alexis Murzeau
> PGP: B7E6 0EBB 9293 7B06 BDBC  2787 E7BD 1904 F480 937F
> _______________________________________________
> cfe-users mailing list
> cfe-users at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-users
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-users/attachments/20200405/6a98550a/attachment.html>


More information about the cfe-users mailing list