[C++, C] Произвольное число аргументов любых типов на C11 и выше с помощью _Generic и variadic макросов
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Функция print на Си, принимающая любые аргументы в любом количествеФункция print на Си, принимающая любые аргументы в любом количествеО себеЯ сам программист на C++, вернее я только начинающий, без коммерческого опыта. Изначально я познакомился с языком Си, а C++ открыл как мощное расширение языка C. В нем на мой взгляд добавлены те необходимые полезные вещи, которых нет в C (перегрузка функций, классы, пространства имен и др), при этом эти вещи отлично расширяют философию языкаЗадумкаЯ узнал что в C стандарта 2011 года добавили небольшую возможность "перегрузки" функций с помощью макроса. (Generic selection) Мне, очень интересно стало написать какую-нибудь функцию, которая максимально использовала бы эту возможностьЗадумка: написать (макро) функцию print, которая выводит через пробел все переданные в нее аргументы. Звучит невероятно для Си, где обычно указывают тип принимаемого аргумента одной буквой в имени, и явно указывают число переданных аргументов. Но с джейнериками из C11 это возможноПростой пример с одним аргументомВвиду в суть работы этой перегрузки по типу на простом примере с одним аргументомНапишем три функции print(x) для типов int, float и char* (cstring):
void print_int(int x) {printf("%d ", x); }
void print_float(float x) {printf("%.4f ", x); }
void print_string(char* x) {printf("%s ", x); }
С помощью данного макроса соединим из под одним именем print:
#define print(x) _Generic((X), int: print_int, float: print_float, char*: print_string)(x)
В итоге получим, что запись print("hi") вызывает print_string("hi"), print(5.5) вызывает print_float(5.5) и так далееПосле обработки препроцессором запись print("hi") превратится в _Generic(("hi"), int: print_int, float: print_float, char*: print_string)("hi"), и компилятор в зависимости от типа первого аргумента выберет имя функции, которую надо подставить вместо всего выражения _Generic(...)Неопределенное число однородных аргументовС помощью макросов также можно передавать неопределенное число аргументов без явного указания их числа. Покажу на примере для функции print_int
void print_int(int n, ...)
{
va_list argptr;
va_start(argptr, n);
int x;
for (int i = 0; i < n; i++)
{
x = va_arg(argptr, int);
printf("%d ", x);
}
va_end(argptr);
}
С помощью макроса, который я любезно скопипастил из гугла :) можем вывести число аргументов n, и передать его в функцию первым аргументомМакрос PP_NARG(...), возвращающий число аргументов
#ifndef PP_NARG
/*
The PP_NARG macro returns the number of arguments that have been
passed to it.
https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s
/
#define PP_NARG(...) PP_NARG_(VA_ARGS,PP_RSEQ_N())
#define PP_NARG_(...) PP_ARG_N(VA_ARGS)
#define PP_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,_63, N, ...) N
#define PP_RSEQ_N() 63,62,61,60, 59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40, 39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20, 19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0
#endif
#define print(...) print_int(PP_NARG(VA_ARGS), VA_ARGS)
Примечание: VA_ARGS содержит в себе аргументы, которые попали в ...Соответственно для джейнериков, если все аргументы одного типа, мы должны написать
#define function(x, ...) Generic((x), int: function_int, float: function_float, char*: function_string)(PP_NARG(VA_ARGS) + 1, x, VA_ARGS)
В итоге получим функцию function, которая обрабатывает неопределенное число однотипных аргументов. Я написал function, а не print, так как для нашей функции это точно не подойдет. Однако если вам известно, что все аргументы одного и того же типа, то такой способ будет намного проще, чем когда любой аргумент любого типаНеопределенное число аргументов любого типаI. Хранение информации о типахМы создадим универсальную функцию вида (синтаксис вольный) hidden_print(sep, n, x1, x2, x3, ...), которая в зависимости от типа следующей переменной xi выполняет нужный printf. Для любой другой реализации можно вызывать нужную функцию с уже известным типомДля определенности максимальное число аргументов будет 12. Для print'а этого достаточно. Так же немного поясню по поводу названийВсе глобальные имена будут начинаться с приставки cool. Это что-то вроде пространства имен, просто я решил создать отдельную несерьезную, удобно подключаемую библиотечку, в которой хранятся такие интересные, но практически не очень полезные штучки. В этой библиотеке на c++ все функции обьявлены в пространстве имен cool, однако в Си пространств имен нет, так что пользуюсь приставками. Однако в любой момент можно сделать #define print cool_print, а затем #undef printДля хранения информации о типах аргументов буду использовать массив cool_hidden_types[12], индекс cool_hidden_last, куда надо добавить следущий элемент cool_hidden_add_int, cool_hidden_add_float и т.д. для каждого типа, которые добавляют в массив значение о типах. Всего наша функция будет поддерживать 7 типов: int, char*, float, double, char (?), uint, longchar (?)Почему-то при записи _Generic(('a'), char: fun_char)() компилятор выдает что-то вроде "не найдена функция для int", так что на практике если передать символ в одинарных кавычках ничего не получится и он дай бог выведется как intЗначения о типах я решил определить с помощью #define, хотя в си есть и enum. Но раз уж тут почти весь код на макросах, то гулять так гулять!Код работы с массивом типов
#define COOL_HIDDEN_INT 0
#define COOL_HIDDEN_STRING 1
#define COOL_HIDDEN_FLOAT 2
#define COOL_HIDDEN_DOUBLE 3
#define COOL_HIDDEN_CHAR 4
#define COOL_HIDDEN_UINT 5
#define COOL_HIDDEN_LONG 6
#define COOL_HIDDEN_VOID 7
int cool_hidden_types[12];
int cool_hidden_last = 0;
void cool_hidden_add_int()
{
cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_INT;
cool_hidden_last += 1;
}
void cool_hidden_add_string()
{
cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_STRING;
cool_hidden_last += 1;
}
/*аналогично для каждого типа */
void cool_hidden_add_void()
{
cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_VOID;
cool_hidden_last += 1;
}
COOL_HIDDEN_VOID будет означать, что данный тип не поддерживается функцией. Можно было бы заморочиться и передавать информацию о размере переменной и выводить в 16-ричном виде для любой другой переменной, но я не стал это делатьСоздадим теперь generic макро функцию cool_hidden_add(x), которая будет добавлять элемент в массив в зависимости от типа x
#define cool_hidden_add(x) Generic((x), int: cool_hidden_add_int, char*: cool_hidden_add_string, float: cool_hidden_add_float, double: cool_hidden_add_double, char: cool_hidden_add_char, unsigned int: cool_hidden_add_uint, long: cool_hidden_add_long, default: cool_hidden_add_void )()
Это было самое простое...II. Определение числа аргументов на уровне макросаИдея заключается в том, чтобы определить макрос вида cool_print##n(x1, x2, ..., xn) ("##" означает конкатенацию со значением n), который по очереди добавляет информацию о типе каждого xi, а затем передает в функцию реализации cool_hidden_print(sep, n, x1, x2, ...) разделитель, n, и все xi. Разделитель я определю как глобальную (если так вообще можно называть переменные с уникальной приставкой) переменную cool_print_sep = " ", которую можно изменить в любой моментОпределим это простым образом через копирование. Хотя наверное их можно было бы сгенерировать макросами, но мне было уже лень. (К тому же у меня и так статический анализатор visual studio заблудился в куче макросов и указывает ошибку там, где все нормально компилируется, но об этом позже)
В общем виде это выглядит так:
#define cool_print_n(x1, x2, x3, x4, ..., xn, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); ................... cool_hidden_add(xn); cool_hidden_print(cool_print_sep, 6, x1, x2, x3, x4, ..., xn)
Полный код
#if 1 or "hide this a big part of code"
#define cool_print_12(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_add(x10); cool_hidden_add(x11); cool_hidden_add(x12); cool_hidden_print(cool_print_sep, 12, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)
#define cool_print_11(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_add(x10); cool_hidden_add(x11); cool_hidden_print(cool_print_sep, 11, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)
#define cool_print_10(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_add(x10); cool_hidden_print(cool_print_sep, 10, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)
#define cool_print_9(x1, x2, x3, x4, x5, x6, x7, x8, x9, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_print(cool_print_sep, 9, x1, x2, x3, x4, x5, x6, x7, x8, x9)
#define cool_print_8(x1, x2, x3, x4, x5, x6, x7, x8, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_print(cool_print_sep, 8, x1, x2, x3, x4, x5, x6, x7, x8)
#define cool_print_7(x1, x2, x3, x4, x5, x6, x7, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_print(cool_print_sep, 7, x1, x2, x3, x4, x5, x6, x7)
#define cool_print_6(x1, x2, x3, x4, x5, x6, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_print(cool_print_sep, 6, x1, x2, x3, x4, x5, x6)
#define cool_print_5(x1, x2, x3, x4, x5, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_print(cool_print_sep, 5, x1, x2, x3, x4, x5)
#define cool_print_4(x1, x2, x3, x4, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_print(cool_print_sep, 4, x1, x2, x3, x4)
#define cool_print_3(x1, x2, x3, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_print(cool_print_sep, 3, x1, x2, x3)
#define cool_print_2(x1, x2, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_print(cool_print_sep, 2, x1, x2)
#define cool_print_1(x, ...) cool_hidden_add(x); cool_hidden_print(cool_print_sep, 1, x)
#endif //"hide this a big part of code"
Немного проспойлерил, что аргументов макро функции cool_print_n всегда 12, и что после нее идет ..., но об этом далееЗначение выполнения макроса PP_NARG(VA_ARGS) не возможно подставить напрямую в выражение cool_print_##PP_NARG(VA_ARGS), так как оно развернется в что-то вроде cool_print_PP_NARG("x", 5, "i", 8,), что не имеет никакого смысла. Поэтому надо использовать код из макроса, но не возвращать число аргументов, а сразу конкатенироватькод PP_NARG(VA_ARGS) еще раз
#ifndef PP_NARG
/*
The PP_NARG macro returns the number of arguments that have been
passed to it.
https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s
/
#define PP_NARG(...) PP_NARG_(VA_ARGS,PP_RSEQ_N())
#define PP_NARG_(...) PP_ARG_N(VA_ARGS)
#define PP_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,_63, N, ...) N
#define PP_RSEQ_N() 63,62,61,60, 59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40, 39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20, 19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0
#endif
cool_print(...) - тот же PP_NARG, но измененный
#define cool_print(...) cool_print_(VA_ARGS , COOL_RSEQ_N())
#define cool_print_(...) COOL_ARG_N(VA_ARGS)
#define COOL_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,63, n, ...) cool_print##n(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11,_12)
#define COOL_RSEQ_N() 63,62,61,60,59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0
Как это работает:
1. Условно происходит вызов cool_print("a", 4, "b")
Это превращается в cool_print_("a", 4, "b", 63,62,61,60,59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0
Затем из cool_print_ это все передается в макрос COOL_ARG_N,
который способен принять 64 аргумента. В 64-й аргумент, названный n
попадает как раз количество исходных аргументов из-за того, что при
добавление аргументов VA_ARGS перед последовательностью COOL_RSEQ_N
(63..0) часть чисел из этой последовательности вытесняется
В конце концов в макросе COOL_ARG_N просиходит конкатенация cool_print_##n
и вызов этого макроса. В данном примере это cool_print_3
cool_print_3("a", 4, "b", 63,62,61,60,59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3)
Всего передается 12 аргументов, так как невозможно обрезать лишние.
И это и не надо, так как благодаря "..." в списке аргументов каждого
макроса cool_print_##n они способны проглотить ненужный хвост
Так же можно было бы сократить число аргументов с 64 до 12,
но я посчитал это не очень важным
III. Собственно реализация функцииОтлично, у нас при вызове cool_print("наши", "аргументы", 10) происходит заполнение массива cool_hidden_types информацией о типе каждого аргумента, а затем вызывается функция реализации cool_hidden_print(int sep, int n, ...)! Давайте напишем эту функциюНебольшое пояснение как в C работать с неопределенным числом аргументов вообщеВ стандартной библиотеке в заголовочном файле <stdarg.h> есть три макроса, созданных для этих целей. Вот порядок действий:
- В обьявлении функции последним параметром надо указать ...
- va_list argptr- это определение указателя argptr, который будет в использоваться в дальнейшем
- va_start(argptr, n)- установка указателя на последний определенный аргумент
- va_arg(argptr, float)- возвращает значение слудующего аргумента
- va_end(argptr)- завершает работу с аргументами
Итого просто в Си без всяких этих макросов нам необходимо вычислять какими-нибудь образом тип следующего аргумента, и условии прекращения перебора аргументовСуть такова: в цикле перебираются все элементы все элементы. В swich'e каждый элемент кастится в void'ый указатель x, а затем вызывается в виде printf("...%s", *((type) x), sep), где type - это тип аргумента, а "..." - это специфичный для данного типа формат вывода. Например для int это printf("%d%s", *((type) x), sep). Для упрощенной записи приведения типов я использую вспомогательный макрос #define COOL_CAST(T, x) ((T) (x))код cool_hidden_print(sep, n, ...)
#define COOL_CAST(T, x) ((T) (x))
void cool_hidden_print(char* sep, int n, ...)
{
va_list argptr;
va_start(argptr, n);
void* x;
for (int i = 0; i < n; i++)
{
switch (cool_hidden_types[i])
{
case COOL_HIDDEN_INT:
x = &va_arg(argptr, int);
printf("%d%s", COOL_CAST(int, x), sep);
break;
case COOL_HIDDEN_STRING:
x = &va_arg(argptr, char*);
printf("%s%s", COOL_CAST(char*, x) , sep);
break;
case COOL_HIDDEN_FLOAT:
x = &va_arg(argptr, float);
printf("%.4f%s", COOL_CAST(float, x), sep);
break;
case COOL_HIDDEN_DOUBLE:
x = &va_arg(argptr, double);
printf("%.4f%s", COOL_CAST(double, x), sep);
break;
case COOL_HIDDEN_CHAR:
x = &va_arg(argptr, char);
printf("%c%s", COOL_CAST(char, x), sep);
break;
case COOL_HIDDEN_UINT:
x = &va_arg(argptr, unsigned int);
printf("%.4u%s", COOL_CAST(unsigned int, x), sep);
break;
case COOL_HIDDEN_VOID:
printf("unsupported type%s", sep);
break;
default:
printf("Internal COOL/CPRINT error line: %d in %s", __LINE__, __FILE__);
break;
}
}
va_end(argptr);
cool_hidden_last = 0;
}
Дополнительные мелочиПосле подключения библиотеки можно избавиться от приставки cool_ с помощью#define print cool_printИдеально, теперь наша функция print полностью работает! В качестве вишенки на торте определим функцию println, которая после вывода переводит нас на новую строку
#define cool_println(...) cool_print(VA_ARGS); printf("\n")
#define cool_printlnn() printf("\n")
К моему большому сожалению я не смог решить проблему, что при вызове макроса без аргументов происходит синтаксическая ошибка из-за лишней запятой в начале, поэтому без аргументов нужно вызывать printlnn()... Я пытался решить это, при определении cool_print
ни так
#define cool_print(...) cool_print_(VA_ARGS , ## COOL_RSEQ_N()
ни так
#define cool_print(...) cool_print_(VA_ARGS ## , COOL_RSEQ_N()
все равно не работает
Как советуют в интернете делать при таком случае ничего не происходит. Видимо эта запись работает только для ,## VA_ARGS, когда VA_ARGS идет в конце, а не в началеВозможно можно как-нибудь еще одним вложенным макросом определить, является ли VA_ARGS пустым. Я нашел в гугле решение только тогда когда максимум 2 аргументаЕще есть еще некий VAR_OPT, который делает как раз то, что нужно, но его добавят, как я понял, в следующем стандартеУ меня почему-то сработало один раз (,), но после перезапуска visual stidio стало VAR_OPT не определено. К тому же в том месте VAR_OPT ставить нельза, так как макрос будет считать что у нас 63 а не 64 аргумента (что приводила к ошибке вызова не того cool_print##n (на единицу меньше). Нужно что-то вроде
#define cool_print(...) VAR_OPT( cool_print_(VA_ARGS , COOL_RSEQ_N()) )
вместо текущего
#define cool_print(...) cool_print_(VA_ARGS , COOL_RSEQ_N())
Проблемы этого метода
- Первое - это конечно же невозможность вызвать функцию без аргументов. К тому же огромным минусом является то, что в случае ошибки генерируется очень невнятное сообщение об ошибке, и я не вижу куда можно вставить его
- Второе - сложность реализации. На C++ аналогичная функция выглядит намного проще. Хотя, имея в качестве шаблона мою функцию print будет не так сложно реализовать любую другую
- Третье - статический анализатор. В visual studio у меня подчеркнут красным каждый print и println со словами "требуется выражение", и висит по одной ошибки (прям красным цветом) на каждый вызов этой функции. Не смотря на это все нормально компилируется. И даже не думаю что на это должно тратиться сильно больше времени, чем на раскрытие variadic templates в c++, хотя я тесты не проводил (а как вообще замерить время компиляции - это отдельный вопрос)Этот минус самый пожалуй критичный. Если знаете как можно подавить эти ложные ошибки, то подскажите в комментариях
При этом компилируется прекрасноПри этом компилируется прекрасноПолный код данной библиотечки можете найти по ссылке на моем гитхабе: print.hА вот пример использования c_example.cЗаключениеУдивительно сколько всего можно сделать на чистом Си с очень слабыми шаблонами. Но все же Си предназначен не совсем для этого. Данная статья нужна в большей мере для интереса, хотя может кому-нибудь и поможет в работе. Для удобств написания кода на Си как раз и был создан C++, который позволяет работать с обьектами через класс, а не писать id обьекта первым параметром в методах; он позваляет делать перегрузку функций для тех случаев, когда это необходимо (чтобы не городить функции типа pow, powi, powf); упрощает работу с указателями, добавляя ссылки, добавляет пространства имен, чтобы не городить приставок, добавляет контейнеры. Но как итог всего этого - медленная компиляцияВот пример реализации этой же функции print на C++:print на C++
#ifndef COOL_PRINT_HPP
#define COOL_PRINT_HPP
#include <string>
#include <iostream>
#include <iomanip>
namespace
{
std::ostream* out = &std::cout;
}
namespace cool
{
inline void setCyrillic()
{
setlocale(LC_ALL, "Russian");
}
void setPrintOut(std::ostream& os)
{
::out = &os;
}
std::ostream* getPrintOutPtr()
{
return ::out;
}
inline void printFlush()
{
*::out << std::flush;
}
inline void print()
{
*::out << ' ';
}
template <typename Arg>
inline void print(const Arg& arg)
{
*::out << std::fixed << std::setprecision(4) << arg << ' ';
}
template <typename Arg, typename... Args>
void print(const Arg& arg, const Args&... args)
{
print(arg);
print(args...);
}
////
inline void println()
{
*::out << '\n';
}
template <typename Arg>
inline void println(const Arg& arg)
{
*::out << std::fixed << std::setprecision(4) << arg << '\n';
}
template <typename... Args>
void println(const Args&... args)
{
print(args...);
println();
}
///
void print0() { }
template <typename Arg>
inline void print0(const Arg& arg)
{
*::out << std::fixed << std::setprecision(4) << arg;
}
template <typename Arg, typename... Args>
void print0(const Arg& arg, const Args&... args)
{
print0(arg);
print0(args...);
}
}
#endif
Более понятно, примерно в 3 раза меньше кода, и реализация более полноценная. Но в то же время стандартный printf хоть и выглядит не так изящно, но зато быстрый и практичный. Каждому языку свое место
===========
Источник:
habr.com
===========
Похожие новости:
- [Microsoft Azure, Облачные сервисы, Компьютерное железо, IT-компании] Утечка утверждает, что Microsoft выпустит этим летом «облачный компьютер»
- [Big Data, Хранение данных, Hadoop, Data Engineering] Cloudera митап про интерактивные SQL запросы к потоковым данным пройдет 27.04 в 16:00 МСК
- [.NET] Свершилось! PVS-Studio поддерживает анализ проектов под .NET 5
- [Социальные сети и сообщества, IT-компании] Внутреннее письмо Facebook показало: в компании решили внушить публике, что утечки данных — это нормально
- [IT-инфраструктура, CRM-системы, Софт] Мсье знают толк в извращениях: взгляд на альтернативы CRM
- [] Finally! PVS-Studio Supports .NET 5 Projects
- [Информационная безопасность, Антивирусная защита, Браузеры, Социальные сети и сообщества] Хакеры создали подставную фирму SecuriElite, чтобы атаковать исследователей безопасности и других хакеров
- [Настройка Linux, DevOps, DIY или Сделай сам, Kubernetes] Как я собрал домашний кластер Kubernetes на базе Raspberry Pi (перевод)
- [PostgreSQL, Облачные сервисы] Использование ClusterControl для аварийного восстановления PostgreSQL в гибридном облаке (перевод)
- [Управление проектами, Развитие стартапа, Управление продуктом, Бизнес-модели, IT-компании] Discord закрыла переговоры: вместо вхождения в состав Microsoft компания выйдет на IPO
Теги для поиска: #_c++, #_c, #_c, #_c++, #__generic, #_generics, #_c11, #_makrosy (макросы), #_peregruzka_funktsij (перегрузка функций), #_c++, #_c
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:31
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Функция print на Си, принимающая любые аргументы в любом количествеФункция print на Си, принимающая любые аргументы в любом количествеО себеЯ сам программист на C++, вернее я только начинающий, без коммерческого опыта. Изначально я познакомился с языком Си, а C++ открыл как мощное расширение языка C. В нем на мой взгляд добавлены те необходимые полезные вещи, которых нет в C (перегрузка функций, классы, пространства имен и др), при этом эти вещи отлично расширяют философию языкаЗадумкаЯ узнал что в C стандарта 2011 года добавили небольшую возможность "перегрузки" функций с помощью макроса. (Generic selection) Мне, очень интересно стало написать какую-нибудь функцию, которая максимально использовала бы эту возможностьЗадумка: написать (макро) функцию print, которая выводит через пробел все переданные в нее аргументы. Звучит невероятно для Си, где обычно указывают тип принимаемого аргумента одной буквой в имени, и явно указывают число переданных аргументов. Но с джейнериками из C11 это возможноПростой пример с одним аргументомВвиду в суть работы этой перегрузки по типу на простом примере с одним аргументомНапишем три функции print(x) для типов int, float и char* (cstring): void print_int(int x) {printf("%d ", x); }
void print_float(float x) {printf("%.4f ", x); } void print_string(char* x) {printf("%s ", x); } #define print(x) _Generic((X), int: print_int, float: print_float, char*: print_string)(x)
void print_int(int n, ...)
{ va_list argptr; va_start(argptr, n); int x; for (int i = 0; i < n; i++) { x = va_arg(argptr, int); printf("%d ", x); } va_end(argptr); } #ifndef PP_NARG
/* The PP_NARG macro returns the number of arguments that have been passed to it. https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s / #define PP_NARG(...) PP_NARG_(VA_ARGS,PP_RSEQ_N()) #define PP_NARG_(...) PP_ARG_N(VA_ARGS) #define PP_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,_63, N, ...) N #define PP_RSEQ_N() 63,62,61,60, 59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40, 39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20, 19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0 #endif #define print(...) print_int(PP_NARG(VA_ARGS), VA_ARGS)
#define function(x, ...) Generic((x), int: function_int, float: function_float, char*: function_string)(PP_NARG(VA_ARGS) + 1, x, VA_ARGS)
#define COOL_HIDDEN_INT 0
#define COOL_HIDDEN_STRING 1 #define COOL_HIDDEN_FLOAT 2 #define COOL_HIDDEN_DOUBLE 3 #define COOL_HIDDEN_CHAR 4 #define COOL_HIDDEN_UINT 5 #define COOL_HIDDEN_LONG 6 #define COOL_HIDDEN_VOID 7 int cool_hidden_types[12]; int cool_hidden_last = 0; void cool_hidden_add_int() { cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_INT; cool_hidden_last += 1; } void cool_hidden_add_string() { cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_STRING; cool_hidden_last += 1; } /*аналогично для каждого типа */ void cool_hidden_add_void() { cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_VOID; cool_hidden_last += 1; } #define cool_hidden_add(x) Generic((x), int: cool_hidden_add_int, char*: cool_hidden_add_string, float: cool_hidden_add_float, double: cool_hidden_add_double, char: cool_hidden_add_char, unsigned int: cool_hidden_add_uint, long: cool_hidden_add_long, default: cool_hidden_add_void )()
В общем виде это выглядит так:
#define cool_print_n(x1, x2, x3, x4, ..., xn, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); ................... cool_hidden_add(xn); cool_hidden_print(cool_print_sep, 6, x1, x2, x3, x4, ..., xn) #if 1 or "hide this a big part of code"
#define cool_print_12(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_add(x10); cool_hidden_add(x11); cool_hidden_add(x12); cool_hidden_print(cool_print_sep, 12, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) #define cool_print_11(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_add(x10); cool_hidden_add(x11); cool_hidden_print(cool_print_sep, 11, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) #define cool_print_10(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_add(x10); cool_hidden_print(cool_print_sep, 10, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) #define cool_print_9(x1, x2, x3, x4, x5, x6, x7, x8, x9, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_add(x9); cool_hidden_print(cool_print_sep, 9, x1, x2, x3, x4, x5, x6, x7, x8, x9) #define cool_print_8(x1, x2, x3, x4, x5, x6, x7, x8, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_add(x8); cool_hidden_print(cool_print_sep, 8, x1, x2, x3, x4, x5, x6, x7, x8) #define cool_print_7(x1, x2, x3, x4, x5, x6, x7, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_add(x7); cool_hidden_print(cool_print_sep, 7, x1, x2, x3, x4, x5, x6, x7) #define cool_print_6(x1, x2, x3, x4, x5, x6, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_add(x6); cool_hidden_print(cool_print_sep, 6, x1, x2, x3, x4, x5, x6) #define cool_print_5(x1, x2, x3, x4, x5, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_add(x5); cool_hidden_print(cool_print_sep, 5, x1, x2, x3, x4, x5) #define cool_print_4(x1, x2, x3, x4, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_add(x4); cool_hidden_print(cool_print_sep, 4, x1, x2, x3, x4) #define cool_print_3(x1, x2, x3, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_add(x3); cool_hidden_print(cool_print_sep, 3, x1, x2, x3) #define cool_print_2(x1, x2, ...) cool_hidden_add(x1); cool_hidden_add(x2); cool_hidden_print(cool_print_sep, 2, x1, x2) #define cool_print_1(x, ...) cool_hidden_add(x); cool_hidden_print(cool_print_sep, 1, x) #endif //"hide this a big part of code" #ifndef PP_NARG
/* The PP_NARG macro returns the number of arguments that have been passed to it. https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s / #define PP_NARG(...) PP_NARG_(VA_ARGS,PP_RSEQ_N()) #define PP_NARG_(...) PP_ARG_N(VA_ARGS) #define PP_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,_63, N, ...) N #define PP_RSEQ_N() 63,62,61,60, 59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40, 39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20, 19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0 #endif #define cool_print(...) cool_print_(VA_ARGS , COOL_RSEQ_N())
#define cool_print_(...) COOL_ARG_N(VA_ARGS) #define COOL_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,63, n, ...) cool_print##n(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11,_12) #define COOL_RSEQ_N() 63,62,61,60,59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0 1. Условно происходит вызов cool_print("a", 4, "b")
Это превращается в cool_print_("a", 4, "b", 63,62,61,60,59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0 Затем из cool_print_ это все передается в макрос COOL_ARG_N, который способен принять 64 аргумента. В 64-й аргумент, названный n попадает как раз количество исходных аргументов из-за того, что при добавление аргументов VA_ARGS перед последовательностью COOL_RSEQ_N (63..0) часть чисел из этой последовательности вытесняется В конце концов в макросе COOL_ARG_N просиходит конкатенация cool_print_##n и вызов этого макроса. В данном примере это cool_print_3 cool_print_3("a", 4, "b", 63,62,61,60,59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3) Всего передается 12 аргументов, так как невозможно обрезать лишние. И это и не надо, так как благодаря "..." в списке аргументов каждого макроса cool_print_##n они способны проглотить ненужный хвост Так же можно было бы сократить число аргументов с 64 до 12, но я посчитал это не очень важным
#define COOL_CAST(T, x) ((T) (x))
void cool_hidden_print(char* sep, int n, ...) { va_list argptr; va_start(argptr, n); void* x; for (int i = 0; i < n; i++) { switch (cool_hidden_types[i]) { case COOL_HIDDEN_INT: x = &va_arg(argptr, int); printf("%d%s", COOL_CAST(int, x), sep); break; case COOL_HIDDEN_STRING: x = &va_arg(argptr, char*); printf("%s%s", COOL_CAST(char*, x) , sep); break; case COOL_HIDDEN_FLOAT: x = &va_arg(argptr, float); printf("%.4f%s", COOL_CAST(float, x), sep); break; case COOL_HIDDEN_DOUBLE: x = &va_arg(argptr, double); printf("%.4f%s", COOL_CAST(double, x), sep); break; case COOL_HIDDEN_CHAR: x = &va_arg(argptr, char); printf("%c%s", COOL_CAST(char, x), sep); break; case COOL_HIDDEN_UINT: x = &va_arg(argptr, unsigned int); printf("%.4u%s", COOL_CAST(unsigned int, x), sep); break; case COOL_HIDDEN_VOID: printf("unsupported type%s", sep); break; default: printf("Internal COOL/CPRINT error line: %d in %s", __LINE__, __FILE__); break; } } va_end(argptr); cool_hidden_last = 0; } #define cool_println(...) cool_print(VA_ARGS); printf("\n")
#define cool_printlnn() printf("\n") ни так
#define cool_print(...) cool_print_(VA_ARGS , ## COOL_RSEQ_N() ни так #define cool_print(...) cool_print_(VA_ARGS ## , COOL_RSEQ_N() все равно не работает #define cool_print(...) VAR_OPT( cool_print_(VA_ARGS , COOL_RSEQ_N()) )
вместо текущего #define cool_print(...) cool_print_(VA_ARGS , COOL_RSEQ_N())
При этом компилируется прекрасноПри этом компилируется прекрасноПолный код данной библиотечки можете найти по ссылке на моем гитхабе: print.hА вот пример использования c_example.cЗаключениеУдивительно сколько всего можно сделать на чистом Си с очень слабыми шаблонами. Но все же Си предназначен не совсем для этого. Данная статья нужна в большей мере для интереса, хотя может кому-нибудь и поможет в работе. Для удобств написания кода на Си как раз и был создан C++, который позволяет работать с обьектами через класс, а не писать id обьекта первым параметром в методах; он позваляет делать перегрузку функций для тех случаев, когда это необходимо (чтобы не городить функции типа pow, powi, powf); упрощает работу с указателями, добавляя ссылки, добавляет пространства имен, чтобы не городить приставок, добавляет контейнеры. Но как итог всего этого - медленная компиляцияВот пример реализации этой же функции print на C++:print на C++ #ifndef COOL_PRINT_HPP
#define COOL_PRINT_HPP #include <string> #include <iostream> #include <iomanip> namespace { std::ostream* out = &std::cout; } namespace cool { inline void setCyrillic() { setlocale(LC_ALL, "Russian"); } void setPrintOut(std::ostream& os) { ::out = &os; } std::ostream* getPrintOutPtr() { return ::out; } inline void printFlush() { *::out << std::flush; } inline void print() { *::out << ' '; } template <typename Arg> inline void print(const Arg& arg) { *::out << std::fixed << std::setprecision(4) << arg << ' '; } template <typename Arg, typename... Args> void print(const Arg& arg, const Args&... args) { print(arg); print(args...); } //// inline void println() { *::out << '\n'; } template <typename Arg> inline void println(const Arg& arg) { *::out << std::fixed << std::setprecision(4) << arg << '\n'; } template <typename... Args> void println(const Args&... args) { print(args...); println(); } /// void print0() { } template <typename Arg> inline void print0(const Arg& arg) { *::out << std::fixed << std::setprecision(4) << arg; } template <typename Arg, typename... Args> void print0(const Arg& arg, const Args&... args) { print0(arg); print0(args...); } } #endif =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:31
Часовой пояс: UTC + 5