Reputation: 183
I'm working on a library where many of our core objects are templates with one particular instantiation showing up in most of the files in the project in the form of a smart pointer to that template instantiation. I explicitly instantiate these templates in a single source file. We recently switched to C++11 and I'm trying to use the new
extern template class MyTemplate<double>;
to speed up the compilation. My first question is whether my use of smart pointers around
MyTemplate<double>
is implicitly instantiating the template and requires the "extern template .." at the top of the file to avoid duplicate instantiation.
My second question is whether there is some alternative to adding all these
extern template class MyTemplate<double>;
to every source file. It just gets a little tedious grepping every instance of the smart pointer for every template I define and making sure I have the correct "extern template" line in that file. I can also see it being a little difficult to enforce this convention for future developers of our code as they may add a template instantiation and forget the appropriate extern template line, especially since no error will be generated.
Upvotes: 6
Views: 2296
Reputation: 49986
I can also see it being a little difficult to enforce this convention for future developers of our code as they may add a template instantiation and forget the appropriate extern template line, especially since no error will be generated.
I dont see how to detect missing extern entries but you can use static assertion that will allow instantiation of your template only for listed types. Those will be types you have explicitly instantiated. Below is example of how to check if T
of your class template is from a list of allowed types (How to make a variadic is_same?):
// Template header
#include <string>
#include <vector>
template<typename T, typename... Rest>
struct is_any : std::false_type {};
template<typename T, typename First>
struct is_any<T, First> : std::is_same<T, First> {};
template<typename T, typename First, typename... Rest>
struct is_any<T, First, Rest...>
: std::integral_constant<bool, std::is_same<T, First>::value || is_any<T, Rest...>::value>
{};
template<typename T>
class MyT {
public:
MyT(); // must not be inline, otherwise no extern magic happens
};
extern template class MyT<double>;
extern template class MyT<std::string>;
// Somwhere in the app code
int main()
{
//MyT<int> dd1; // error
MyT<std::string> dd2; // ok
MyT<double> dd3; // ok
}
// Template implementation file
template<typename T>
MyT<T>::MyT() {
static_assert(is_any<T, double, std::string>::value, "ups... you forgot to make it extern template!");
}
template class MyT<double>;
template class MyT<std::string>;
Upvotes: 0
Reputation: 171127
If you know for certain that you'll be explicitly instantiating the template, simply put the explicit instantiation declaration (the extern template
line) into the header, so it gets included with the template.
There is no problem with this line being present in the instantiating file—the standard explicitly allows it, as long as the explicit instantiation definition (the non-extern
one) comes after the explicit instantiation declaration (the extern
one). The ruling is in C++14 14.7.2/11; the same ruling is present in C++11 too.
Upvotes: 7
Reputation: 25409
You can put the extern template
declarations right into your header file where the template
is defined. For example:
In file useful.hxx
:
#ifndef USEFUL_HXX
#define USEFUL_HXX
namespace my
{
template <typename T>
T
do_useful_stuff(T x)
{
return x;
}
extern template int do_useful_stuff(int);
extern template float do_useful_stuff(float);
// Potentially many more...
} // namespace my
#endif // ifndef USEFUL_HXX
In file useful.cxx
:
#include "useful.hxx"
namespace my
{
template int do_useful_stuff(int);
template float do_useful_stuff(float);
// Potentially many more...
} // namspace my
And finally in file main.cxx
:
#include <iostream>
#include "useful.hxx"
int
main()
{
std::cout << my::do_useful_stuff(42) << std::endl;
std::cout << my::do_useful_stuff(1.0f) << std::endl;
}
You can now compile useful.cxx
and main.cxx
and then link both together.
So far so good. However, as you can see, this is still quite repetitive. If you're not afraid of some preprocessor magic, you can factor the common stuff out into a file useful.txx
#ifndef MY_EXTERN
#error "Please '#define MY_EXTERN' to 'extern' or '' before '#include'ing this file."
#endif
namespace my
{
MY_EXTERN template int do_useful_stuff(int);
MY_EXTERN template float do_useful_stuff(float);
// Potentially many more...
} // namspace my
and then #include
it in useful.cxx
#include "useful.hxx"
#define MY_EXTERN /* empty*/
#include "useful.txx"
#undef MY_EXTERN
and useful.hxx
#ifndef USEFUL_HXX
#define USEFUL_HXX
#ifndef MY_USE_EXTERN_TEMPLATES
#define MY_USE_EXTERN_TEMPLATES 0
#endif
namespace my
{
template <typename T>
T
do_useful_stuff(T x)
{
return x;
}
} // namespace my
#if MY_USE_EXTERN_TEMPLATES
#define MY_EXTERN extern
#include "useful.txx"
#undef MY_EXTERN
#endif
#endif // ifndef USEFUL_HXX
like shown. Note that I've also used the opportunity to make the extern
declarations conditional so clients can use your header file with either instantiation model. Therefore, we update the main.cxx
as well:
#define MY_USE_EXTERN_TEMPLATES 1
#include <iostream>
#include "useful.hxx"
int
main()
{
std::cout << my::do_useful_stuff(42) << std::endl;
std::cout << my::do_useful_stuff(1.0f) << std::endl;
}
And again, we can compile useful.cxx
and main.cxx
and link the resulting object files together. If we don't #define MY_USE_EXTERN_TEMPLATES
to 1 in main.cxx
, the file could be compiled and linked in one flush.
Upvotes: 5