Как на чистом C передать имя типа в функцию?

Рейтинг: 1Ответов: 2Опубликовано: 21.05.2023

Нужно передать в функцию имя типа на этапе выполнения (не компиляции!!! Макроопределение не подходят!). Как это сделать?

Ответы

▲ 6

В C нет ни шаблонов, ни RTTI как в C++, или подобных механизмов, отсутствует рефлексия или интроспекция. Но есть несколько способов:

  • variadic arguments (va_arg(args,<type>))

    void f(const char* fmt, ...) {
        va_list args;
        va_start(args,fmt);
        for (const char* ptr = fmt; *ptr != '\0'; ++ptr) {
            if (*ptr == 'd') {
                double arg = va_arg(args,double);
            }
        }
        va_end(args);
    }
    
  • type erasure (T* -> void* -> T*)

    typedef enum { /* ... */ } Type;
    
    typedef struct {
        Type type;
        void* ptr;
    } Test;
    
    void f(Test* test) {
        if (test->type == TYPE) {
            T* ptr = test->ptr;
            ptr->implementation;
        }
    }
    
    int main() {
        T t;
        Test test;
        test.type = TYPE;
        test.ptr = (void*)&t;
        f(&test);
        return 0;
    }
    
  • union:

    #include <stdio.h>
    
    typedef enum {
        FLOAT, DOUBLE, LDOUBLE,
        INT, SHORT, LONG, UINT, USHORT, ULONG
    } Type;
    
    typedef struct {
        Type type;
        union {
            float f;
            double d;
            long double ld;
            int si;
            short ss;
            long sl;
            unsigned ui;
            unsigned short us;
            unsigned long ul;
        };
    } Test;
    
    void f(Test* test) {
        switch (test->type) {
            case FLOAT:   printf("float %f",test->f); break;
            case DOUBLE:  printf("double %lf",test->d); break;
            case LDOUBLE: printf("long double %Lf",test->ld); break;
            case INT:     printf("int %d",test->si); break;
            case SHORT:   printf("short %hd",test->ss); break;
            case LONG:    printf("long %ld",test->sl); break;
            case UINT:    printf("unsigned int %u",test->ui); break;
            case USHORT:  printf("unsigned short %hu",test->us); break;
            case ULONG:   printf("unsigned long %lu",test->ul); break;
        }
    }
    
    int main() {
        Test test;
    
        test.type = DOUBLE;
        test.d = 3.14159;
        f(&test);
    
        test.type = USHORT;
        test.us = (unsigned short)31459;
        f(&test);
    
        return 0;
    }
    
▲ 1

В Си невозможно корректно принять внешние данные просто так, ваша программа должна знать протокол связи, структуры пакетов, и алгоритм обработки. Всё точно так-же как на передающей стороне. Иначе получится каша. Всё данные что создаются внутри программы - уже имеют известный тип, и могут быть обработаны на уровне предварительной компиляции. Можно упростить обработку, позволив компилятору самостоятельно выбирать функции обработки данных. Для этого есть макрос "_Generic((X)". Пример - https://github.com/AVI-crak/Rtos_cortex/blob/master/sPrint.h

#define dpr_(X) _Generic((X),           \
const char*:        soft_print_con, \
char*:              soft_print,     \
char:               print__char,    \
uint8_t:            print_uint8,    \
uint16_t:           print_uint16,   \
uint32_t:           print_uint32,   \
uint64_t:           print_uint64,   \
int8_t:             print_int8,     \
int16_t:            print_int16,    \
int32_t:            print_int32,    \
int64_t:            print_int64,    \
float:              print_float,    \
double:             print_double,   \
default:            print_hex       \

)(X)

Кроме того вам наверняка понадобится древний злой макрос передачи неизвестного типа данных в саму функцию "attribute((transparent_union))". Это древнее зло позволяет отправить в функцию всё что угодно без приведения типа. И если сама функция будет знать что это такое (+1 дополнительный параметр) - то внутри можно работать с данными через объединение, без ошибок и предупреждений.