0

I am writing a pretty huge program with lots of templated functions. Naturally, the program has a long compile-time, which is why I wanted to use forward declaration. Now there are a lot of functions and I do not want to write the forward declaration for each single one of them. Even more so, I want to be able to add some functions without having to add the forward declarations manually.

A quick example:

#define max_dim 3
template bool match_any<1>();
template bool match_any<2>();
template bool match_any<3>();

If I set max_dim to another value, I do not want to manually add the additional forward declarations. Until now I have used a python-script, to just generate a file with all the forward declarations for me.

My goal now is to avoid the python-script and to do this using the cpp-preprocessor only (if this is possible). More concretly, I want to be able to just change the max_dim and all neccessary forward-declarations are generated.

I now that loops etc. are possible just with the preprocessor; what I do not know is how to actually make the preprocessor generate the declarations.

wittn
  • 298
  • 5
  • 16
  • 1
    These are not forward declarations, they are _explicit instantiations_. – user17732522 Oct 12 '22 at 14:41
  • given the forward declarations are probably in a header file (you mentioned a huge program, so I condlude that), your code generation approach looks fine for me. With preprocessor you cannot write to both a .cpp and a .h file. – BitTickler Oct 12 '22 at 14:42
  • 1
    It is not actually possible to have proper loops in the preprocessor and approximating them manually is probably going to be more work than manually producing these declarations. You would probably want to use something ready-made like [`BOOST_PP_REPEAT_FROM_TO`](https://www.boost.org/doc/libs/1_80_0/libs/preprocessor/doc/ref/repeat_from_to.html) from `boost::preprocessor`. – user17732522 Oct 12 '22 at 14:51
  • "I now that loops etc. are possible just with the preprocessor": uh ??? –  Oct 12 '22 at 15:08
  • @BitTickler: what do you mean ? –  Oct 12 '22 at 15:09

2 Answers2

1

I'm not the right one to ask if what you want to do is possible in a better way, but this is definitely solvable using the preprocessor.

I'll use file iteration, although other methods are also possible:

// slot.h
#ifndef A_0
# include "a.h"
#else
# include "b.h"
#endif


// a.h
#if (SLOT) & 1
# define A_0 1
#else
# define A_0 0
#endif
#if (SLOT) & 2
# define A_1 2
#else
# define A_1 0
#endif
#if (SLOT) & 4
# define A_2 4
#else
# define A_2 0
#endif
#if (SLOT) & 8
# define A_3 8
#else
# define A_3 0
#endif

#undef VAL
#define VAL (A_3|A_2|A_1|A_0)

#undef B_0
#undef B_1
#undef B_2
#undef B_3


// b.h
#if (SLOT) & 1
# define B_0 1
#else
# define B_0 0
#endif
#if (SLOT) & 2
# define B_1 2
#else
# define B_1 0
#endif
#if (SLOT) & 4
# define B_2 4
#else
# define B_2 0
#endif
#if (SLOT) & 8
# define B_3 8
#else
# define B_3 0
#endif

#undef VAL
#define VAL (B_3|B_2|B_1|B_0)

#undef A_0
#undef A_1
#undef A_2
#undef A_3



// impl.cpp
#ifndef VAL
#define VAL 12
#endif

template bool match_any<VAL>();

#define SLOT (VAL-1)
#include "slot.h"
#if VAL != 0
#include __FILE__
#endif

(https://godbolt.org/z/csehns44j)

If you need more than 4 bits to represent the value of max_dim, then you'd need to add a few lines to a.c and b.c. This uses self recursive includes, which only works for a few hundred iterations (without special compiler flags). To circumvent this, you can use the following structure:

// iter1.h
#if CONTINUE
#include "iter2.h"
#if CONTINUE
#include "iter2.h"
#if CONTINUE
#include "iter2.h"
#if CONTINUE
#include "iter2.h"
#if CONTINUE
#include "iter2.h"
// ...
#endif
#endif
#endif
#endif
#endif

// iter2.h
#if CONTINUE
#include "iter3.h"
#if CONTINUE
#include "iter3.h"
#if CONTINUE
#include "iter3.h"
#if CONTINUE
#include "iter3.h"
#if CONTINUE
#include "iter3.h"
// ...
#endif
#endif
#endif
#endif
#endif

// iter3.c
#if CONTINUE
#include FILE
#if CONTINUE
#include FILE
#if CONTINUE
#include FILE
#if CONTINUE
#include FILE
#if CONTINUE
#include FILE
// ...
#endif
#endif
#endif
#endif
#endif



// impl.cpp
#ifndef VAL
#define VAL 100
#define FILE "impl.cpp"
#endif

template bool match_any<VAL>();

#define SLOT (VAL-1)
#define CONTINUE VAL != 0
#include "slot.h"

#ifndef ONCE
#define ONCE
#include "iter1.h"
#endif

(https://godbolt.org/z/h74jPb11c)

With 3 iter.h files, of which each has 5 includes that results in 5^3 iterations. Expanding this should be trivial.

camel-cdr
  • 643
  • 4
  • 8
  • Out of curosity, what other methods would there be? max_dim would be a value probably never higher than 10 (but I have lots of methods which need to be declared) so probably I will not need lots of files, but still trying to avoid unneccessary ones and if possible, I'd love to keep it in one file. – wittn Oct 12 '22 at 19:28
  • 1
    @wittn the other possibility I was thinking of would be: using macro expansion to emulate integer subtraction (decrement), and using macro recursion to do the loop. But if you basically only need 10 steps, then you could just hardcore everything:https://godbolt.org/z/7rd6rWc6E – camel-cdr Oct 12 '22 at 19:35
  • This seems great. I do not want to hardcode this, because I want to give the option of how high the max_dim (if the dev only needs 2 dims and I hardcode 5, the compile-time is gigantic for just 2 dims) is and also I have a lot of functions (not just match_any). – wittn Oct 13 '22 at 08:55
  • One follow up question: how would this look like, if I want two int parameters instead of one: REP10(GEN) (resulting in template bool match_any<1,1>();). – wittn Oct 13 '22 at 09:02
  • Also, is there a more elegant way to choose based on the defined max_dim which REP is to be choosen than '#if maxdim == 1 REP1 #endif #else....' etc.? – wittn Oct 13 '22 at 14:24
  • 1
    @wittn yes, you can use the `##` operator, something like this: https://godbolt.org/z/7fzc77jYc – camel-cdr Oct 13 '22 at 19:50
  • Thanks for your help until here. I have one final question related to my comment above: I want to have multiple 'GENS'. Depending on which is used, want to be able to pass n arguments (e.g. I want a function 'template bool match_any();', but also ' 'template bool match_any();' etc.). As GEN is passed to REP as a function, I would need for any number of arguments a new rep block. To complicate it more, I also want to repeat other things than numbers (just some strings), so I'd need even more. Is it possible to do it like in a style of 'GEN(REP_INT(10), REP_STRING(5))' or similar?Thanks! – wittn Oct 14 '22 at 12:05
  • How is that the solution to the question, given you still have to implement all of the specializations manually? – BitTickler Oct 14 '22 at 15:47
  • No I do not; through the presented method I just need to write it once for every method and the rep-method is going to care of the specializations. The only questionmark left is about multiple arguments. – wittn Oct 14 '22 at 20:02
-2

It is doubtful if the idea below is an improvement for you, but here is how it could be done.

// in some header file:
#define MAX_DIM 3
#ifndef __INSTANCE_IMPL
#  define TINSTANCE(n) \
   extern template bool match<(n)>;
#else
#  define TINSTANCE(n) \
   template bool match<size_t N = (n)> {
     // however it is implemented
   }
#endif

TINSTANCE(1)
TINSTANCE(2)
TINSTANCE(3)

// in the .cpp file:
#define __INSTANCE_IMPL
#include <extra-header>
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • 2
    __INSTANCE_IMPL is a reserved name in C++ and should not be used in your own code. – Neil Butterworth Oct 12 '22 at 14:55
  • Whatever - MS also uses __ in COM programming. Rename it to a MickeyMouse prefix if you think that helps. – BitTickler Oct 12 '22 at 14:56
  • 2
    What OP is calling "_forward declarations_" are actually _explicit instantiation definitions_ and they only make sense if they are put in a separate `.cpp`, not a header. The part with `{ // however it is implemented }` therefore doesn't make sense either. Also `__INSTANCE_IMPL` is a reserved identifier. It shouldn't be used for this. – user17732522 Oct 12 '22 at 14:56
  • Of course it makes sense. It avoids the double bookkeeping of declaration and instantiation in 2 different files. – BitTickler Oct 12 '22 at 14:57
  • 3
    MS, as a C++ implementor, is allowed to do that. – Neil Butterworth Oct 12 '22 at 14:57
  • Then rename it to MickeyMouse ;) – BitTickler Oct 12 '22 at 14:58
  • @BitTickler `template bool match<(n)> { // however it is implemented }` is under no circumstances correct syntax. Again, these are not actually declarations or definitions of function or class templates. – user17732522 Oct 12 '22 at 14:58
  • How so? With the information given, I concluded that there is a (forward) declaration and an implementation. He did not show how his implementation looks like and so I just gave the idea. – BitTickler Oct 12 '22 at 14:59
  • @BitTickler OP is using the wrong terminology for what they mean, which is obvious from their goal of reducing compilation time of template instantiations and the code they are showing. A declaration introduced by `template` without `<` following it is an explicit instantiation, not a declaration of a template. – user17732522 Oct 12 '22 at 15:02
  • Now I think I understand what you mean.... editing... – BitTickler Oct 12 '22 at 15:03
  • https://en.cppreference.com/w/cpp/language/function_template#Explicit_instantiation, https://en.cppreference.com/w/cpp/language/class_template#Explicit_instantiation – user17732522 Oct 12 '22 at 15:11
  • If it is strictly about compile times because of lengthy template instantiations in header files, which are included in multiple source files, you could also check out "precompiled headers". MS cl.exe as well as clang support that. And maybe it helps to speed up your build process. https://clang.llvm.org/docs/PCHInternals.html – BitTickler Oct 12 '22 at 15:35