Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should visibility/export macros be applied to templates when writing a library?

When building a C++ DLL or shared library __attribute__((__visibility__("default"))) or __declspec(dllexport) is frequently attached via a macro to those concrete symbols (classes, functions, etc) that should be made available to consumers of the library, with the other symbols defaulted to have internal visibility.

But what should be done about inline functions or templates?

It seems that for inline functions the answer should be that no annotation is needed. If the consumer of the header defining the inline function actually inlines the function, then there is no symbol needed. If the consumer emits an out-of-line definition instead, that is still OK. The only wrinkle is that the definitions of the inline function inside the DLL and inside each consuming library may differ. So, you could get some trouble if you were expecting to reliably compare the addresses of inline functions, but that seems pretty sketchy anyway.

Given that argument, it would seem that since templates bodies are typically entirely visible to the consuming TU, that the same logic would apply.

I feel like there are probably some subtleties here with respect to 'extern template' and explicit instantiations.

Does anyone have concrete guidance on how visibility attributes should adhere to inline functions and templates?

like image 715
acm Avatar asked Jul 06 '14 02:07

acm


1 Answers

Inline functions are not externally visible (have no linkage, IIRC), so they are not exportable from a DLL. If they are to be public, then they are fully written in the header file of your library and every user recompiles it.

And as you say in the question, since the inlined code is recompiled in every module that uses the library, so for future versions of the library there may be problems.

My advice for inline functions in a shared library is that they should be used only for really trivial tasks or for absolutely generic functions. Note that converting a public inline function to a non-inline function is an ABI breaking change.

For example:

  • A memcpy-like funcion. Inline!
  • A bswap-like function. Inline!
  • A class constructor. Do not inline! Even if it does nothing now, You may want to do something in a future version of the library. Write a non-inline empty constructor in the library and export it.
  • A class destructor. Do not inline! Same as above.

The fact that an inline function can have several different addresses, in practice, has little importance.

About extern template and explicit instantiations, with a bit of care, they can be used to export a template from a library. If the template instantiations are limited to a specific set of cases, you can even avoid to copy the template code to the header files.

NOTE 1: In the following examples I will use a simple function template, but a class template will work exactly the same. NOTE 2: I'm using the GCC syntax. The MSC code is similar, I think you already know the differences (and I don't have a MSC compiler around to test).

Example 1

public_foo.h

template<int N> int foo(int x); //no-instantiable template

shared_foo.cpp

#include "public_foo.h"

//Instantiate and export

template __attribute__ ((visibility("default")))
int foo<1>(int x);

template __attribute__ ((visibility("default")))
int foo<2>(int x);

program.cpp

#include "public_foo.h"

int main()
{
    foo<1>(42); //ok!
    foo<2>(42); //ok!
    foo<3>(42); //Linker error! this is not exported and not instantiable
}

If instead, your template should be freely instantiable, but you expect it to be frequently used in a particular way, you can export these ones from the library. Think of std::basic_string: it is most likely to be used as std::basic_string<char> and std::basic_string<wchar_t>, but unlikely as std::basic_string<float>.

Example 2

public_foo.h

template<int N> int foo(int x)
{
    return N*x;
}

//Do not instantiate these ones: they are exported from the library
extern template int foo<1>(int x);
extern template int foo<2>(int x);

shared_foo.cpp

#include "public_foo.h"

//Instantiate and export

template __attribute__ ((visibility("default")))
int foo<1>(int x);

template __attribute__ ((visibility("default")))
int foo<2>(int x);

program.cpp

#include "public_foo.h"

int main()
{
    foo<1>(42); //ok, from library
    foo<2>(42); //ok, from library
    foo<3>(42); //ok, just instantiated
}
like image 62
rodrigo Avatar answered Sep 24 '22 07:09

rodrigo