1

This problem came to us while developing our engine. We want to have

template <typename T>
std::pair<const uint8_t*, size_t> get_resource()
{
    return {nullptr, 0ull};
}

and specialized for many Ts. What's the important part is that we want specializations live in separate source (.cpp) files and compiled into our engine's static library binary. Placing specializations in source files is vital since it is possible that those functions will be frequently changed and #included by other headers of huge codebase (at least a few hundred thousand LoC) of the engine. So in case of them living in header files, it could cause rebuilding of large part of the project upon changing one of specializations' definitions. I couldn't find help for such a problem, so here I'm posting our solution.

Criss
  • 581
  • 5
  • 17
  • Not a duplicate, but this answer might be what you arrived at. https://stackoverflow.com/a/8131212/8372853 – cigien Mar 20 '20 at 18:59

1 Answers1

2

First of all: there's no problem in placing function template specialization in source file since - when specialized - it's a function just like any other. What's tricky is letting sources #includeing the header with template definition that such specialization exists. Apparently there's no way in c++ to declare specialization. What you can do is force instantiation of get_resource template for some T (which we obviously need to do anyway because we want specializations be compiled into library binary). This can be done with following syntax:

//force instantiation of get_resource for T=SOME_TYPE
template std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>();

But: if we place it in header file, just after template definition, we want be able to specialize it because you can't specialize something which is already instantied. If we place it in source file, just after specialization, sources #includeing the header with template definition won't be able to see it because it was never declared to exist. That's the moment when extern template comes to the rescue. Forced instantiation with extra extern keyword is kind of declaration that this template function will be instantied for some given T (SOME_TYPE in example above), but not yet. So what we can do now is use this extern keyword to declare forced instantiation in header file, then in source file define our specialization and after that do actual forced instantiation (without extern). This will make sure that specialization is visible for sources #includeing it and at the same time its definition can be placed in source file (compiled as separate translation unit).

TL;DR

Header file

template <typename T>
std::pair<const uint8_t*, size_t> get_resource()
{
    return {nullptr, 0ull};
}

extern template std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>();

Source file

template<> std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>()
{
    return {reinterpret_cast<const uint8_t*>("whatever"), 9ull};
}

template std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>();
Criss
  • 581
  • 5
  • 17
  • If anyone having hard time to adapt it to member function like me, just move function definition to outside of class like this: `extern template std::pair MyClass::get_resource();` – Kao Mar 09 '23 at 02:19