What for?
Let's imagine that we need to write a set of functions that differ from each other only by a couple of keywords (and, as a rule, one of them is the type name). Well, for example, take a look at the functions that calculate the sum of the elements of arrays for different types
(for the sake of simplicity, checks of pointers to the inequality to zero are omitted / * for simplicity's sake, the possibility of overflow is also not considered for int - approx. lane * /)
void sum_float(int n, float *a, float *b) { int i; for(i=0;i<n;i++) a[i]+=b[i]; } void sum_double(int n, double *a, double *b) { int i; for(i=0;i<n;i++) a[i]+=b[i]; } void sum_int(int n, int *a, int *b) { int i; for(i=0;i<n;i++) a[i]+=b[i]; }
Well, you should agree, how much better would it be to describe the function body once, specifying the name of the received (and returning) type as a “parameter”, and then determine the function instances for specific types? And these functions are still relatively simple, but imagine if they were longer, and their set is more.
That's it for this case in C ++ there is a keyword template. But alas, not in pure S.
')
Now you will see that sometimes this keyword can be emulated by means of the good old C preprocessor.
Patterns in C.
We need some ingredients.
1: Blanks
First, let's define a couple of macros. They will be located in a separate header file, and we still need this file. For clarity, let's call this separate header file "templates.h"
templates.h
#ifndef TEMPLATES_H_ #define TEMPLATES_H_ #define CAT(X,Y) X##_##Y #define TEMPLATE(X,Y) CAT(X,Y) #endif
We will need the Template macro later to combine X and Y macros as X_Y, so that writing TEMPLATE (function, type) we would get function_type in this place.
In preprocessor C, the directive ## allows you to combine two tokens into one. The reason why we use two macros instead of one #define TEMPLATE (X, Y) X ## Y is that if X, in turn, is also a macro ... However, it does not matter. This question is beyond the scope of this article.
2: Cooking
Any normal function should be in the file with the .c extension and its prototype should be described in the .h file, right? Well, let's write them already. To designate a parameter corresponding to the type of data for which the function is intended, we will traditionally use the letter “T”. You will later meet her in the directives #define.
sum_as_template.h
#ifdef T #include "templates.h" void TEMPLATE(sum,T)(int n, T *a, T *b)
Surely, you have already noticed that there is no typical construction in this header file to protect against re-enabling #ifndef HEADER_H #define HEADER_H ... #endif. And this is no accident, and then we'll come back to this point. On the other hand, #ifdef T is not necessarily mandatory, but is very useful in case the header file is included and the type is not defined. And then the error messages may not be very informative.
And now C
sum_as_template.c
#ifdef T #include "templates.h" void TEMPLATE(sum,T) (int n, T *a, T *b) { int i; for(i=0;i<n;i++) a[i]+=b[i]; } #endif
3. Serve.
I do not remember how many lines we wrote before, but for a resume you can safely multiply their number by 3. Or 4?
all_possible_sums.c
#include "templates.h" #include "all_possible_sums.h" #ifdef T #undef T #endif #define T float #include "sum_as_template.c" #ifdef T #undef T #endif #define T double #include "sum_as_template.c" #ifdef T #undef T #endif #define T int #include "sum_as_template.c"
Little things in life: for GCC 3 lines #ifdef T #undef T #endif can be replaced by one #undef T, but here's Visual C ++ (at least up to version 7 inclusive, does not tolerate such liberties)
Well, and hiiiiiiiiider!
all_possible_sums.h
#ifndef ALL_POSSIBLE_SUMS_H_ #define ALL_POSSIBLE_SUMS_H_ #include "templates.h" #ifdef T #undef T #endif #define T float #include "sum_as_template.h" #ifdef T #undef T #endif #define T double #include "sum_as_template.h" #ifdef T #undef T #endif #define T int #include "sum_as_template.h" #endif
Now it should be clear why we did not protect sum_as_template.h from multiple inclusions: we include it once for each type involved.
4. Serve
Well, actually, that's all. You can call:
main.c
#include "all_possible_sums.h" int main(int argc, char **argv) { int ai[3] = {1,2,3}; int bi[3] = {4,5,6}; float af[3] = {1.0,2.0,3.0}; float bf[3] = {1.5,2.5,3.5}; TEMPLATE(sum,int)(3,ai,bi); TEMPLATE(sum,float)(3,af,bf); return 0; }
And one more thing.
An inquisitive reader will ask a translator, but what if I need the type of "unsigned long long"? After all, we get the function “void sum_unsigned long long ()”? Fortunately for the translator, the author has provided for this. Use typedef:
typedef unsigned long long uint64; TEMPLATE(sum,uint64)
instead
TEMPLATE(sum,unsigned long long)
(This is a rather free
translation . My article is too lazy to write, since Google knows the answer to the function function in plain c question, and I absolutely have nothing to add to that article, but since it’s not in Google’s Russian language do not be lost, post post-mortem)
- 8 <- [here ended the original post] -
UPD: I want to say a huge thank you to the GRAFIN99
habraouser for at least 4 detected errors in the source code, and three of them are by sight, simply during the reading of the article.