📜 ⬆️ ⬇️

Instantiation of function templates by type list (Part 1)

Have you ever written a function template that should be instantiated for a specific set of types and nothing else? If not, then this article is unlikely to seem interesting to you. But if you are still here, then we will begin.

The article will consist of two parts. In the first part, the problem will be described and its primary, slightly crooked, solution will be presented. The second part will be devoted to the improvement and generalization of the solution.

First we describe the problem. Imagine you declare a function template in a header file. If the template should be potentially suitable for everything that is possible, then it should be defined here in the header file. This entails end-to-end dependencies, an increase in compile time and srach in the header file. But it is still inevitable. Of course, you can define a template in another header file and include it at the bottom of the file with the advertisement. This will save you from the third problem, but it will not get rid of the first two. Now the opposite situation, when the template should be used (instantiated) only for a couple of specific types. Then you safely transfer the definition to the source and explicitly instantiate your template for each individual type. A little time consuming when accompanied, but still better than shitting in the headline.
')
Our situation is somewhere in the middle. There is a function template, and it must be instantiated for a specific type list that somewhere in your project is perpetuated with typedef. Well, for example:
typedef TypeList<int,char,bool,string, EmptyList> MyTypeList. 

The fact that such a list of types can be read by A. Alexandrecu in “Modern Design in C ++”, and an example of implementation is here .
Under katom self-made implementation (the same as the thousands of others, probably). I personally like it more, because it allows me to write
typedef TypeList<int,char,bool,string, EmptyList> MyTypeList;
instead of the classic record
typedef TypeList<int,TypeList<char,TypeList<bool,TypeList<string, EmptyList>>>> MyTypeList;

 struct EmptyList{}; template<typename Head, typename... Tail> struct TypeList { typedef Head head_t; typedef TypeList<Tail...> tail_t; }; template<typename Head> struct TypeList<Head, EmptyList> { typedef Head head_t; typedef EmptyList tail_t; }; 

Let's return to the topic. You can have fifty different function templates in the project, and each of them should be instantiated only for this omnipresent list of types. How to do better:
1) Defining a pattern in the header file is a defeatist attitude.
2) Determine the template in the source and specialize it manually for all types from the list ... yeah, and then correct it in 50 places, if the list grows, decreases or just changes.

Both options are bad. The purpose of this article is to show how you can define a template in the source and get rid of manual instantiation for each type. To make the goal a little clearer and to awaken your appetite, just give the final result. But how it is implemented will be discussed only in the next part of the article.
 -------------------------------- typelists.h       -------------------------------- #pragma once struct EmptyList{}; template<typename Head, typename... Tail> struct TypeList { typedef Head head_t; typedef TypeList<Tail...> tail_t; }; template<typename Head> struct TypeList<Head, EmptyList> { typedef Head head_t; typedef EmptyList tail_t; }; typedef TypeList<int, double, bool, char, const char*, EmptyList> MyTypeList; .....      ,    ... -------------------------------- myclass.h -------------------------------- #pragma once class MyClass { public: template<typename T> void f(T x); }; -------------------------------- myclass.cpp -------------------------------- #include "templateclass.h" #include "typelist.h" #include <iostream> namespace { InstantiateMemFunc(MyClass, f, MyTypeList) // (*) } template<typename T> void MyClass::f(T x) { std::cout<< x << "\n"; } -------------------------------- main.cpp -------------------------------- #include <typelist.h> #include "myclass.h" int main() { MyClass tc; tc.f(3); tc.f(2.0); tc.f(true); tc.f('a'); tc.f("hello world"); return 0; } 


I hope, now it became clear that there is a problem and that there is a goal. Note that usually we would have to insert the following code into the source code of myclass.cpp so that this program could be linked:
 template<> void MyClass::f<int>(int); template<> void MyClass::f<double>(double); template<> void MyClass::f<bool>(bool); template<> void MyClass::f<char>(char); template<> void MyClass::f<const char*>(const char*); 

Here, let everyone judge for himself what he likes more, this or the line with the star in myclass.cpp.

The remainder of the first part of the article will be devoted to deciding whether to instantiate a pattern for the type list in the source file. The only thing that the first solution will do well is that it will work. And the second part of the article will open the veil of what is behind the InstantiateMemberFunction expression in the file myclass.cpp.

So let's get down to business, we need to instantiate a function template for the type list. We divide the problem into two subtasks:
1) how to do something for a list of types and
2) how to instantiate a pattern for one particular type.

Let's start with the first subtask. Here, except how to steal the reception with recursion, nothing comes to mind. Well, for example, like this:

 template<typename Types> struct SomethingDoer; // ,  -     template<> struct SomethingDoer<EmptyList> //     { static void doSomething(...) //  -  , .    {} }; template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > //    . { static void doSomething() { ...  -     - Head .... (**) SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); //       } }; 

Now the goal of the task becomes very clear. In the line with two asterisks, you need to instantiate the desired function for one particular type - Head.
Let's move to the second subtask.
Question: How can I instantiate a function template? 5 second.
Time is over. Answer: explicitly and implicitly.

Obviously, as we have already discussed, it is too time consuming when accompanied. What about implicitly?
Question: What are two ways to implicitly instantiate a function template? 10 Seconds. Ok, another 10 ten seconds. But now the time is up.
Answer:
Method one is to call a function so that the template parameter can be displayed or directly specified;
The second way is to define a variable that has the type of instance of the template and assign it to ... uh, how would it be better to say ... the address of the template (yes, I know, the template does not have an address, but I don’t know how to call it differently).
An example is better than a random word set:
 template <typename T> void f() {...} template <typename T> void g(T t) {...} struct S { template <typename T> void memFunc(){...} }; f<int>(); //   g(58); //   void (*gPtr)(int) = g; //   void (S::*memFuncPtr)() = &S::memFunc<- , , int>; //   

About that which way is better for us, I will not strongly rassusolivat, but it will drag on for a long time. I'll say right away - the second one will work better.
Since you agree with me, let's try to instantiate the template in the second way.
 template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > { typedef void (MyClass::*MemFuncType)(Head); static void doSomething() { MemFuncType x = &MyClass::f; (void)(x); //    ,     SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); } }; 

Voila, the mechanism for instantiating templates according to the type list has been created ... but not yet implemented. Imagine, you wrote the above definition of the class SomethingDoer along with its doSomething method somewhere in the nameless namespace of the source file, in this case in myclass.cpp. Will the MyClass :: f (T) template be instantiated for the desired type list? Unfortunately not. But how to force the above code to do what it was created for. Yes, very simple. You need to call him:
 SomethingDoer<MyTypeList>::instantiate(); 

Only where is this wonderful line to write? There in the nameless namespace? She does not return anything, it can not be assigned to any variable. Okay, I'll have to wrap it up:
 struct Instantiator { Instantiator() { SomethingDoer<MyTypeList>::instantiate(); } }; 

I don't know what your compiler is, but gcc-4.8.1 should compile this code. And in debug mode, it is possible to link. But no more than that. What happens in production mode (release)? All that is not used in the case, will be thrown to dog bran. And the most important thing is not used in the case, namely: the local variable x from the doSomething method and the constructor of the Instantiator class. But it's not a problem. You just need to convince the compiler that these two things are still very important. With - it's simple. You can, for example, declare a variable volatile, and let only the compiler dare to do something with it. And with the constructor Instantiator - take and declare a variable of type Instantiator .
Here is how our source will now look:
 #include "myclass.h" #include "typelist.h" #include <iostream> namespace { template<typename Types> struct SomethingDoer; template<> struct SomethingDoer<EmptyList> { static void doSomething(...) {} }; template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > { typedef void (MyClass::*MemFuncType)(Head); static void doSomething() { volatile MemFuncType x = &MyClass::f; (void)(x); SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); } }; template <typename TList> struct Instantiator { Instantiator() { SomethingDoer<TList>::doSomething(); } }; Instantiator<MyTypeList> a; //     . } template<typename T> void MyClass::f(T x) { std::cout<< x << "\n"; } 

That's it, at least we have already coped with the task. The template is defined in the source instead of the header file and instantiated for a list of types. All changes in the required type list will automatically be reflected in the mechanism of the instantiation, and we will not have to prescribe explicit instances of the template manually if the type list changes. Only one tiny one is striking in the eyes: all this code is the utter deformity that your colleagues and you yourself have dozens of “faeces” and “chnee” when you open it later.

Before specifying what exactly is badly this solution, let us sum up the subtotals by collecting all the accumulated code in one place (under the cut).
 // myclass.h #pragma once class MyClass { public: template<typename T> void f(T x); }; // myclass.cpp #include "templateclass.h" #include "typelist.h" #include <iostream> namespace { template<typename Types> struct SomethingDoer; template<> struct SomethingDoer<EmptyList> { static void doSomething(...) {} }; template<typename Head, typename... Tail> struct SomethingDoer<TypeList<Head, Tail...> > { typedef void (MyClass::*MemFuncType)(Head); static void doSomething() { volatile MemFuncType x = &MyClass::f; (void)(x); SomethingDoer<typename TypeList<Head, Tail...>::tail_t>::doSomething(); } }; template <typename TList> struct Instantiator { Instantiator() { SomethingDoer<TList>::doSomething(); } }; Instantiator<MyTypeList> a; //     . } template<typename T> void MyClass::f(T x) { std::cout<< x << "\n"; } // typelists.h #pragma once struct EmptyList{}; template<typename Head, typename... Tail> struct TypeList { typedef Head head_t; typedef TypeList<Tail...> tail_t; }; template<typename Head> struct TypeList<Head, EmptyList> { typedef Head head_t; typedef EmptyList tail_t; }; typedef TypeList<int, double, bool, char, const char*, EmptyList> MyTypeList; // main.cpp #include <typelist.h> #include "myclass.h" int main() { MyClass tc; tc.f(3); tc.f(2.0); tc.f(true); tc.f('a'); tc.f("hello world"); return 0; } 

The main disadvantage of this solution, in addition to its deformity, is that it is private. We instantiated the pattern of one particular function of one particular class. For another class, we would have to do the same thing in another source. And if there were some patterns, then you would have to slightly expand the doSomething method, although this is the least of all the evils. The biggest evil is that each user will have to understand how this is done, because the code of the instantiation mechanism and the launch code of this mechanism are closely intertwined. It would be nice to hide the mechanism, no matter how cumbersome and confusing it might be. Yes, so hide, so that the user was enough to write:
  InstantiateMemFunc(MyClass, f, MyTypeList) // (*) 

But we will talk about this in the second part.

Source: https://habr.com/ru/post/235831/


All Articles