2

I have a fixed set of four types A, B, C and D, and a large amount of class and function templates that work with these types.

To reduce compile times, I would like to put the definition of these templates into .cpp files and explicitly instantiate the templates for this fixed set of types. However, the explicit instantiation introduces a lot of boilerplate code, which I would like to reduce. Is there an elegant way of explicitly instantiating function and class templates for a fixed set of types?

Here's some code to illustrate the problem:

#include <iostream>

class A { public: int foo() const { return 0; } };
class B { public: int foo() const { return 1; } };
class C { public: int foo() const { return 2; } };
class D { public: int foo() const { return 3; } };


template<typename T>
class SomeClass {
    /* could have a member variable of type A-D */
    T m_t;
    
    /* or several functions which take such a type */
    void printFoo(const T& t){
        std::cout << t.foo() << "\n";
    }
};


/* normal explicit instantiation */
//template class SomeClass<A>;
//template class SomeClass<B>;
//template class SomeClass<C>;
//template class SomeClass<D>;


/* or something with macros, but hopefully better than this: */

#define INSTANTIATE_FOR_ALL_TYPES \
INSTANTIATE_WITH(A) \
INSTANTIATE_WITH(B) \
INSTANTIATE_WITH(C) \
INSTANTIATE_WITH(D)

/* if this here could be one line instead of three, then you found the answer */
#define INSTANTIATE_WITH(TYPE) template class SomeClass<TYPE>;
INSTANTIATE_FOR_ALL_TYPES
#undef INSTANTIATE_WITH


int main(){
    return 0;
}

I wouldn't use explicit instantiation if it weren't certain from the design of the program that the types won't change. Also, I'm aware that for compiling the code once, the compile time is not affected by explicit instantiation. However, when writing tests which include many templates, and recompilation is done often, the effect is very noticeable. If there's another option to achieve shorter compilation times, I'm open to it.

RL-S
  • 734
  • 6
  • 21
  • 1
    What is the benefit you'd expect over just explicitly instantiating it, as you've already done in the code? Eventually you'll need to tell the compiler which templates to instantiate anyway... – andreee Jul 02 '21 at 13:31
  • C++20 **modules** ought to improve compile times (I say *ought* because I haven't tried it myself). **Large-Scale C++ Software Design** by John Lakos dedicates a large part of the book to techniques for making compile times as short as possible. (Yes, it's an old book, but it's still 99% relevant.) – Eljay Jul 02 '21 at 13:33
  • @andreee It's four lines with exactly the same content, except for the type name. I'd like to reduce boilerplate code. The duplicates become especially noticeable when I want to instantiate a number of functions with several parameters, instead of one class. – RL-S Jul 02 '21 at 13:35
  • @Eljay That sounds interesting. It doesn't *immediately* answer my question, but I'll put that on my reading list :) – RL-S Jul 02 '21 at 13:37
  • Well, I think then macros are the way to go (I don't think you can collapse this to a single line in C++). Maybe [`__VA_ARGS__`](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html) could help? But I'm not aware of any possibility to extract single parameters from `__VA_ARGS__`, which you'd obviously need for replacement. That's my two cents :-) – andreee Jul 02 '21 at 13:45
  • Update: Well, looks like [it is possible](https://stackoverflow.com/a/1872506/1753435). Doesn't look trivial though... – andreee Jul 02 '21 at 13:47
  • How would extracting an argument help? To me it seems like what is required is passing a callable, which is then invoked with the four types. – RL-S Jul 02 '21 at 14:29
  • My idea was to have a macro that you could call using `INSTANTIATE_CLASS_WITH_TYPES(SomeClass, A, B, C, D, ...)`, which would expand to the commented out instantiations, since this appears to be what you want. I don't know where a callable would help here, all this can only be done at a preprocessor level. An explicit class template instantiation statement is a _definition_, so there's no "C++ way" to condense it further. Think about it: there's also no way to shorten `int a, float b, char c;`, so what you're ultimately trying to achieve is, in other words, text replacement. – andreee Jul 02 '21 at 16:30
  • I don't need to specify the types in the macro call. They're always the same types, see my question for that. What isn't always the same is the classes or functions (and their arguments) which need instantiation. So pseudo-code with a callable for what I want is: ```#define INSTANTIATE( ARG(TYPE) ) ARG(A); ARG(B); ARG(C); ARG(D)``` ```INSTANTIATE(SomeClass)``` – RL-S Jul 02 '21 at 21:08

1 Answers1

0

For now, until someone finds a better solution, the following works well enough:

#define INSTANTIATE_FOR_ALL_TYPES(TYPE) template class SomeClass<TYPE>;
#include "instantiator.hpp"

with the contents of instantiator.hpp being

/* lack of a header guard is intentional */
INSTANTIATE_FOR_ALL_TYPES(A)
INSTANTIATE_FOR_ALL_TYPES(B)
INSTANTIATE_FOR_ALL_TYPES(C)
INSTANTIATE_FOR_ALL_TYPES(D)

#undef INSTANTIATE_FOR_ALL_TYPES

Typos and similar mistakes are caught by the preprocessor. This works well for instantiating several functions/classes with minimal code duplication:

#define INSTANTIATE_FOR_ALL_TYPES(TYPE) \
template class SomeClass<TYPE>; \
template class AnotherClass<TYPE>; \
template void foo( const TYPE& );
#include "instantiator.hpp"

If someone can spot problems with this approach, I'd welcome the feedback. But I'd prefer "this could cause problem x under circumstances y" over "macros are evil" and "this is not how you should use #include".

RL-S
  • 734
  • 6
  • 21