Зачем?
Давайте представим себе, что нам нужно написать набор функций, которые отличаются друг от друга лишь парой ключевых слов (и, как правило, одно из них — название типа). Ну, вот, например, взгляните на функции, рассчитывающие суммы элементов массивов для разных типов (упрощения ради, проверки указателей на неравенство нулю опущены /*упрощения ради также не рассматривается возможность переполнения для int — прим. пер.*/)
void sum_float(int n, float *a, float *b)
{
/* computes a:=a+b where a and b are two arrays of length n */
int i;
for(i=0;i<n;i++) a[i]+=b[i];
}
void sum_double(int n, double *a, double *b)
{
/* computes a:=a+b where a and b are two arrays of length n */
int i;
for(i=0;i<n;i++) a[i]+=b[i];
}
void sum_int(int n, int *a, int *b)
{
/* computes a:=a+b where a and b are two arrays of length n */
int i;
for(i=0;i<n;i++) a[i]+=b[i];
}
Ну согласитесь же, насколько бы было лучше описать тело функции один раз, указав названия принимаемого (и возвращаемого) типа в виде «параметра», а потом определить экземпляры функции для конкретных типов? И эти функции еще относительно просты, а представьте себе, если бы они были длиннее, а их набор — больше.
Вот именно для этого случая в C++ существует ключевое слово template. Но увы, не в чистом С.
Сейчас вы увидите, что иногда это ключевое слово можно эмулировать средствами старого доброго препроцессора языка С.
Шаблоны в С.
Нам понадобятся некоторые ингридиенты.
1: Заготовки
Для начала, определим пару макросов. Они будут располагаться в отдельном заголовочном файле, и этот файл нам еще понадобится. Для ясности, назовем этот отдельный заголовочный файл «template.h»
template.h
#ifndef TEMPLATES_H_
#define TEMPLATES_H_
#define CAT(X,Y) X##_##Y
#define TEMPLATE(X,Y) CAT(X,Y)
#endif
Макрос Template нам понадобиться в дальнейшем чтобы объединять макроопределения X и Y в виде X_Y, таким образом, чтобы написав TEMPLATE(function,type) мы получили бы в этом месте function_type.
В препроцессоре С директива ## позволяет объединить два токена в один. Причиной, по которой мы здесь используем два макроса вместо одного #define TEMPLATE(X,Y) X##Y является то, что если X, в свою очередь, тоже будет макроопределением… Впрочем, неважно. Этот вопрос выходит за пределы рассматриваемого в этой статье.
2: Готовим
Любая нормальная функция должна находиться в в файле с расширением .c и ее прототип должен быть описан в файле .h, верно? Ну так, давайте уже их напишем. Чтобы обозначить параметр, соответствующий типу данных, для которых предназначена функция, традиционно будем использовать букву «T». Вы потом еще встретите ее в директивах #define.
sum_as_template.h
#ifdef T
#include "templates.h"
void TEMPLATE(sum,T)(int n, T *a, T *b);
#endif
Наверняка вы уже заметили, что в этом заголовочном файле отсутствует типовая конструкция для защиты от повторного включения #ifndef HEADER_H #define HEADER_H… #endif. И это неспроста, и мы потом еще к этому моменту вернемся. С другой стороны, #ifdef T не то чтобы обязателен, но очень полезен на тот случай, если заголовочный файл включен, а тип не определен. А то сообщения об ошибках могут быть не очень информативными.
А теперь С
sum_as_template.c
#ifdef T
#include "templates.h"
int TEMPLATE(sum,T) (int n, T *a, T *b)
{
/* computes a:=a+b where a and b are two arrays of length n */
int i;
for(i=0;i<n;i++) a[i]+=b[i];
}
#endif
3. Сервируем.
Не помню, сколько строк мы до этого написали, но для резюме вы смело можете умножить их число на 3. Или 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"
Мелочи жизни: для GCC 3 строки #ifdef T #undef T #endif можно заменить на одну #undef T, но вот Visual C++ (как минимум, до 7 версии включительно, не переносит подобных вольностей)
Ну, и Хииииииииидер!
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
Теперь должно быть понятно, почему мы не защищали sum_as_template.h от множественных включений: мы включаем его по разу на каждый задействованный тип.
4. Подаем
Ну, собственно, и все. Можно вызывать:
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;
}
И вот еще что.
Пытливый читатель спросит переводчика, а что если мне нужен тип «unsigned long long»? Ведь у нас получится функция «void sum_unsigned long long()»? К счастью для переводчика, автор предусмотрел и это. Используйте typedef:
typedef unsigned long long uint64;
TEMPLATE(sum,uint64)
вместо
TEMPLATE(sum,unsigned long long)
(Это довольно-таки вольный перевод. Свою статью писать уже лень, раз гугл знает ответ на вопрос function template in plain c, и к той статье мне решительно нечего добавить, но раз на хабре и вообще в рускоязычном секторе ответ гуглом не находится, чтобы добру не пропадать, опубликую пост-мортем)
Автор: alphysic