Как стать автором
Обновить

Шаблоны на C. Да! На чистом С. Не С++

Время на прочтение4 мин
Количество просмотров64K

Зачем?


Давайте представим себе, что нам нужно написать набор функций, которые отличаются друг от друга лишь парой ключевых слов (и, как правило, одно из них — название типа). Ну, вот, например, взгляните на функции, рассчитывающие суммы элементов массивов для разных типов (упрощения ради, проверки указателей на неравенство нулю опущены /*упрощения ради также не рассматривается возможность переполнения для 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: Заготовки

Для начала, определим пару макросов. Они будут располагаться в отдельном заголовочном файле, и этот файл нам еще понадобится. Для ясности, назовем этот отдельный заголовочный файл «templates.h»

templates.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"    
void 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, и к той статье мне решительно нечего добавить, но раз на хабре и вообще в русскоязычном секторе ответ гуглом не находится, чтобы добру не пропадать, опубликую пост-мортем)

— 8< — [здесь закончился оригинальный пост] —

UPD: Хочу сказать огромное спасибо хабраюзеру GRAFIN99 за как минимум 4 выявленные ошибки в исходниках, причем три из них — на глаз, просто в ходе прочтения статьи.
Теги:
Хабы:
Всего голосов 81: ↑65 и ↓16+49
Комментарии65

Публикации

Истории

Работа

Программист С
39 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
19 сентября
CDI Conf 2024
Москва
20 – 22 сентября
BCI Hack Moscow
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
25 сентября
Конференция Yandex Scale 2024
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн