1

I have cases which I make an alias to specific template instance. In my code previous to c++11, it was, for example:

typedef std::vector<std::string> My;

But I want to use the new language capabilities for making only one instance. That would be:

template class std::vector<std::string>;//in source file
extern template class std::vector<std::string>;//in header file
using My = std::vector<std::string>;//in header file

I have a long list of such typedef's in one header file (they are all in same context). Is there a way to do it without repeating each template 3 times?

Doron Ben-Ari
  • 443
  • 3
  • 12
  • "making only one instance" - can you clarify this? I'm not sure what you mean – kmdreko Jan 04 '19 at 06:46
  • kmdreko, Template classes are not real classes. The actual type is created only when the template is used with the specific parameters. This creation is called "template instantiation". To handle this, compilers are creating instance in each module, and only one is used for link. To avoid this and make only one instance, in C++11, one can use template class ... to instantiate a template in source file, and use extern template class... in the header file which is included by other modules. My problem is that when I make an alias, I find myself repeating the template 3 times. – Doron Ben-Ari Jan 04 '19 at 08:15
  • No there are no other ways. – Oliv Jan 04 '19 at 09:21

1 Answers1

1

There is no nice way to do it in C++, but there is a hacky way. This is not recommended in any way, unless the alternative is very high.

The idea is to use C++-11 variadic macros (usually a bad idea):

#define DECLARE_AND_ALIAS(Alias, TemplateStart, ...) \
   extern template class TemplateStart __VA_OPT__(,)  __VA_ARGS__; \
   using Alias = TemplateStart __VA_OPT__(,) __VA_ARGS__

These define the two things you need in the header file. Now your example becomes:

template class std::vector<std::string>;//in source file
DECLARE_AND_ALIAS(My, std::vector<std::string>); //in header file

You still need to repeat the same template twice, which you told you'd like to avoid. In that case, it is possible to resort to a greater trickery. You can make your header file behave differently depending on who includes the header file, just like the way dllexport and dllimport are handled

#ifdef INSTANTIATE_TEMPLATE
#  define DECLARE_AND_ALIAS(Alias, TemplateStart, ...) \
     template class TemplateStart __VA_OPT__(,) __VA_ARGS__; \
     using Alias = TemplateStart __VA_OPT__(,)  __VA_ARGS__
#else
#  define DECLARE_AND_ALIAS(Alias, TemplateStart, ...) \
     extern template class TemplateStart __VA_OPT__(,) __VA_ARGS__; \
     using Alias = TemplateStart __VA_OPT__(,) __VA_ARGS__
#endif

Now, this is imperfect since you don't control for which templates to generate code and which to avoid. For a fine-grained solution things get messy. You can pass a third parameter, that will be responsible for the use of the extern keyword:

#define DECLARE_AND_ALIAS(Extern, Alias, TemplateStart, ...) \
     Extern template class TemplateStart __VA_OPT__(,) __VA_ARGS__; \
     using Alias = TemplateStart __VA_OPT__(,) __VA_ARGS__

And the usage

#define MyExtern // compiling My.cpp
#define My2Extern extern // not compiling My2.cpp
DECLARE_AND_ALIAS(MyExtern, My, std::vector<std::string>);
DECLARE_AND_ALIAS(My2Extern, My2, std::map<int, std::string>);

This only barely matches the requirements of the questions, but it does use the variadic macro parameters of C++11. This is not a clean solution, and probably not what you hoped for.

When modules are introduced (hopefully C++20, but possibly only in C++23) this whole extern trickery will go away, since the import keyword will do the right thing with respect to code generation of templates.

Michael Veksler
  • 8,217
  • 1
  • 20
  • 33