Как объединить эти почти идентичные функции C?

#c #templates

#c #шаблоны

Вопрос:

Функции C, приведенные ниже, идентичны друг другу, за исключением того, что имеют разные прототипы. Есть ли какой-нибудь аккуратный способ их как-то объединить?

     /* convert strings into numbers within range */
    int ciphart_str2size_t(
        char f, const char *s, size_t min, size_t max, size_t *out
    ) {
        *out = 0;
        int i, num;
        for (i = 0; s[i] >= '0' amp;amp; s[i] <= '9'; i  ) {
            num = s[i] - '0';
            if (*out > (max - max % 10)/ 10 - num   max % 10) {
                ciphart_err("'-%c %s' is larger than maximum '%zu'", f, s, max);
                return RETURN_FAIL_ARGS;
            }
            *out *= 10;
            *out  = num;
        }
        if (s[i] != '') {
            ciphart_err("'-%c %s' contains illegal symbol '%c'", f, s, s[i]);
            return RETURN_FAIL_ARGS;
        }
        if (*out < min) {
            ciphart_err("'-%c %s' is smaller than minimum '%zu'", f, s, min);
            return RETURN_FAIL_ARGS;
        }
        return RETURN_OK;
    }

    int ciphart_str2uint64_t(
        char f, const char *s, uint64_t min, uint64_t max, uint64_t *out
    ) {
        *out = 0;
        int i, num;
        for (i = 0; s[i] >= '0' amp;amp; s[i] <= '9'; i  ) {
            num = s[i] - '0';
            if (*out > (max - max % 10)/ 10 - num   max % 10) {
                ciphart_err("'-%c %s' is larger than maximum '%zu'", f, s, max);
                return RETURN_FAIL_ARGS;
            }
            *out *= 10;
            *out  = num;
        }
        if (s[i] != '') {
            ciphart_err("'-%c %s' contains illegal symbol '%c'", f, s, s[i]);
            return RETURN_FAIL_ARGS;
        }
        if (*out < min) {
            ciphart_err("'-%c %s' is smaller than minimum '%zu'", f, s, min);
            return RETURN_FAIL_ARGS;
        }
        return RETURN_OK;
    }
 

Я также собираюсь добавить больше его вариантов, int например. Следовательно, я думаю, что консолидация поможет уменьшить ожидаемые ошибки в моем коде.

Я добавил templates в качестве тега, так как думаю, что речь идет о концепции шаблона. Но ответы могут быть с любым другим методом, кроме шаблонов. Я просто думаю, что людям, которые смотрят шаблоны, может быть интересно прокомментировать это.

Комментарии:

1. Я был бы удивлен, если uint64_t и size_t не являются одним и тем же типом.

2. Разве это не (max - max % 10)/ 10 то же max/10 самое, что для целых чисел?

3. Если производительность не критична, вы можете сэкономить код, внедрив версию для uintmax_t и заставив другие функции вызывать эту версию. (Им потребуется временная переменная типа uintmax_t , которую нужно скопировать в их *out .)

4. @dbush Я бы совсем не удивился. Некоторые микроконтроллеры имеют действительно странные размеры в этом отношении.

5. @caveman Имеют ли значение размеры типов? Значение имеют числовые диапазоны.

Ответ №1:

Вы можете определить макрос для шаблона определения функции:

 #define DEFINE_CIPHART_STR2(type)                                              
int ciphart_str2##type(char f, const char *s, type min, type max, type *out)   
{                                                                              
   /* lots */                                                                  
   /* of   */                                                                  
   /* code */                                                                  
   /* here */                                                                  
   return RETURN_OK;                                                           
}
 

Затем вызовите макрос, чтобы определить функции из шаблона:

 DEFINE_CIPHART_STR2(size_t)
DEFINE_CIPHART_STR2(uint64_t)
 

РЕДАКТИРОВАТЬ 1: поскольку вы используете printf спецификаторы формата в функциях, вам, вероятно, потребуется также передать часть спецификатора формата в качестве отдельного параметра:

 #define DEFINE_CIPHART_STR2(type, pri)                                         
int ciphart_str2##type(char f, const char *s, type min, type max, type *out)   
{                                                                              
   /* lots */                                                                  
   /* of   */                                                                  
   /* code */                                                                  
   ciphart_err("'-%c %s' is larger than maximum '%" pri "'", f, s, max);       
   /* here */                                                                  
   return RETURN_OK;                                                           
}
 
 DEFINE_CIPHART_STR2(size_t, "zu")
DEFINE_CIPHART_STR2(uint64_t, PRIu64)
 

( PRIu64 макрос определяется #include <inttypes.h> .)

Альтернативой является приведение печатаемых значений в соответствие со спецификаторами.


Чтобы сэкономить место для кода в исполняемом файле, вместо определения нескольких больших, почти идентичных функций, вы могли бы определить одну функцию, которая работает для самого большого неподписанного типа, и определить небольшие функции-оболочки для каждого из меньших неподписанных типов.

Пример функции для самого большого неподписанного типа:

 int ciphart_str2uintmax_t(char f, const char *s, uintmax_t min, uintmax_t max, uintmax_t *out)
{
    /* lots */
    /* of   */
    /* code */
    /* here */
    return RETURN_OK;
}
 

Пример функции-оболочки для меньшего неподписанного типа:

 int int ciphart_str2size_t(char f, const char *s, size_t min, size_t max, size_t *out)
{
    uintmax_t vout;
    int ret = ciphart_str2uintmax_t(f, s, min, max, amp;vout);

    *out = vout;
    return ret;
}
 

Для определения этих функций-оболочек можно использовать макрос шаблона (не обернутую функцию), но, поскольку они довольно малы, вероятно, оно того не стоит.

Комментарии:

1. РЕДАКТИРОВАТЬ: я добавил вариант макроса шаблона, чтобы также передавать спецификатор формата, соответствующий типу.

Ответ №2:

Вы пытаетесь вернуть два значения: код ошибки и «выходное» значение.

Для достижения этой цели существует несколько способов.

Обратите внимание, что uint64_t [вероятно] больше, чем size_t [или наоборот]. Выберите один. Или определите тип, который больше, чем все из них.


Сделайте последний аргумент an int * , чтобы принять код возврата. Это int в обеих ваших функциях. И измените возвращаемое значение на out значение:

 #define RETURN(code_) 
    do { 
        *err = code_; 
        return out; 
    } while (0)

/* convert strings into numbers within range */
uint64_t
ciphart_common(char f, const char *s, size_t min, size_t max, int *err)
{
    int i, num;

    out = 0;

    for (i = 0; s[i] >= '0' amp;amp; s[i] <= '9'; i  ) {
        num = s[i] - '0';
        if (out > (max - max % 10) / 10 - num   max % 10) {
            ciphart_err("'-%c %s' is larger than maximum '%zu'", f, s, max);
            RETURN(RETURN_FAIL_ARGS);
        }
        out *= 10;
        out  = num;
    }

    if (s[i] != '') {
        ciphart_err("'-%c %s' contains illegal symbol '%c'", f, s, s[i]);
        RETURN(RETURN_FAIL_ARGS);
    }

    if (out < min) {
        ciphart_err("'-%c %s' is smaller than minimum '%zu'", f, s, min);
        RETURN(RETURN_FAIL_ARGS);
    }

    return RETURN(RETURN_OK);
}
 

Учитывая количество имеющихся у вас аргументов (например, 5), мне иногда нравится создавать a struct , содержащий параметры.

Преимущество этого заключается в том, что если у вас есть несколько функций, которые должны работать с этими значениями, это экономит кучу стека push / pop.

Кроме того, у вас может быть много возвращаемых значений.

 struct param {
    char f;
    const char *s;
    size_t min;
    size_t max;

    int err;
    uint64_t out;
}

/* convert strings into numbers within range */
int
ciphart_common(struct param *p)
{
    int num;
    const char *s = p->s;
    size_t max = p->max;

    p->out = 0;

    for (;  (*s >= '0') amp;amp; (*s <= '9');    s) {
        num = *s - '0';

        if (p->out > (max - max % 10) / 10 - num   max % 10) {
            ciphart_err("'-%c %s' is larger than maximum '%zu'",
                p->f, p->s, max);
            return RETURN_FAIL_ARGS;
        }

        p->out *= 10;
        p->out  = num;
    }

    if (*s != '') {
        ciphart_err("'-%c %s' contains illegal symbol '%c'",
            p->f, p->s, *s);
        return RETURN_FAIL_ARGS;
    }

    if (p->out < p->min) {
        ciphart_err("'-%c %s' is smaller than minimum '%zu'",
            p->f, p->s, p->min);
        return RETURN_FAIL_ARGS;
    }

    return RETURN_OK;
}
 

Ответ №3:

Вы могли бы определить шаблон как макрос:

 #define ciphart_generic_err(Fmt, F, S, T) _Generic( 
    (T), 
    char                  : ciphart_err(Fmt " '%c'",   F, S, T), 
    int                   : ciphart_err(Fmt " '%i'",   F, S, T), 
    unsigned int          : ciphart_err(Fmt " '%u'",   F, S, T), 
    long int              : ciphart_err(Fmt " '%li'",  F, S, T), 
    unsigned long int     : ciphart_err(Fmt " '%lu'",  F, S, T), 
    long long int         : ciphart_err(Fmt " '%lli'", F, S, T), 
    unsigned long long int: ciphart_err(Fmt " '%llu'", F, S, T)  
)

#define ciphart_str_convert_impl(F, S, Min, Max, Out) 
do { 
    *Out = 0; 
    int i, num; 
    for (i = 0; S[i] >= '0' amp;amp; S[i] <= '9'; i  ) { 
        num = S[i] - '0'; 
        if (*Out > (Max - Max % 10)/ 10 - num   Max % 10) { 
            ciphart_generic_err("'-%c %s' is larger than maximum", F, S, Max); 
            return RETURN_FAIL_ARGS; 
        } 
        *Out *= 10; 
        *Out  = num; 
    } 
    if (S[i] != '') { 
        ciphart_generic_err("'-%c %s' contains illegal symbol", F, S, S[i]); 
        return RETURN_FAIL_ARGS; 
    } 
    if (*Out < Min) { 
        ciphart_generic_err("'-%c %s' is smaller than minimum", F, S, Min); 
        return RETURN_FAIL_ARGS; 
    } 
    return RETURN_OK; 
} while (0)
 

а затем определите свои различные реализации.

 int ciphart_str2size_t(
    char f, const char *s, size_t min, size_t max, size_t *out
){
    ciphart_str_convert_impl(f, s, min, max, out);
}

int ciphart_str2uint64_t(
    char f, const char *s, uint64_t min, uint64_t max, uint64_t *out
){
    ciphart_str_convert_impl(f, s, min, max, out);
}

// ... (any other proto)
 

Комментарии:

1. Как должна выглядеть функция в макросе, чтобы не зависеть от типа?

2. Да, вы правы, я не подумал о форматировании печати.

3. Я добавил общую функцию шаблона печати. Если включен параметр -Wformat, вы получите несколько предупреждений. Либо игнорируйте их, либо отключите -Wformat .