#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 .