Комментарии 30
что символ формата после "%" неверного типа.
Просто хочу напомнить, что stdint.h с uint32_t / uint_least16_t, и inttypes.h с PRIu32 / SCNuLEAST16 и т.д. появились еще в C99 (и по наследству в C++11). Да это может не очень удобно (т.к. format строку приходится писать как "%«SCNuLEAST16), но все равно все понятно.
Пара комментов:
1. Почему в качестве идентификатора типа не использоваться сразу format specifier? То есть вместо 'd' для double сразу писать "%lf"? Тогда можно избавиться от многоэтажного if… if else ..., а сразу использовать нужный формат — и код проще, и работает быстрее.
2. Действительно ли есть необходимость печатать массивы? Просто _Generic() — это C11, все нормальные (я не про MVSC сейчас) компиляторы его поддерживают, а __builtin_types_compatible_p() — расширение GCC. К тому же по крайней мере массивы char'ов _Generic() вполне нормально должен понимать, я его активно использую (с GCC), и проблем с этим не возникало.
3. Не скажу, что это прям реально нужно для печати, но я вот использую аналогичный механизм, когда мне надо к SQL statement'у прибайндить всякие значения, и намного удобнее написать что-то типа sql_bind(stmt, foo, 5, «bar», 7), чем несколько отдельных bind_int(), bind_text() и т.д.
4. Еще это дико полезно, когда есть свои типы, с которыми хочется уметь работать, «как с родными». Например, я использую в одном проекте свои «умные строки», и хочется не вспоминать каждый раз, «умная» это строка или нет, а просто указывать ее в качестве параметра тому же bind'у.
Спасибо!
сразу писать "%lf"?
- Так сначала и было, но потом возникли идеи с цветом, с печатью char и как символа и как числа, к тому же так любой пользователь может просто открыть интересующий "этаж if/else" и сделать так как ему надо (кто-то любит всё hex).
необходимость печатать массивы?… _Generic() — это C11
- В библиотеке над которой я работаю, очень удобно печатать массивы. Подумаю сделать #ifdef на будущее и использовать _Generic там где не обнаружится поддержки builtin-ов.
sql_bind(stmt, foo, 5, «bar», 7)
- Однозначно да, биндить к запросу очень было бы удобно. Подумаю, как-нибудь, можно ли генерализовать это в библиотеку, скажем va_arg_generic.
свои типы, с которыми хочется уметь работать, «как с родными».
- Вот ещё даже не пробовал совсем ничего такого с user-defined types, алиасами, структурами, работает?
Хорошая, годная магия. Но вот если бы язык развивался, и в коммитете занимались бы делом, то давно можно было бы сделать что-то подобное плюсовой libfmt или питоновскому fmt. Вынести это на уровень компилятора и радоваться.
Пожалуйста, не надо. Комитет Си отлично справляется со своей работой и поддерживает нормальное поступательное развитие того, что действительно нужно.
На мой взгляд комитет никак не работает, а начиная с С99 работает только во вред.
Если в чай добавить полный пакет сахара то выходит не очень, а если немного то даже очень. Можно, и даже нужно, в Си добавить немного сахара и для этого не обязательно ломать совместимость и прозрачность.
Это просто жесть какая-то, один из самых используемых языков в мире совсем не развивается.
Комитет — это про лебедь, рак и щуку. У них нет концепции развития языка.
Вот _Generic, сделали сугубо для математической библиотеки.
Нити добавили в стандартную библиотеку и тут же обгадились, что нельзя было pthreads засунуть?
Анонимные структуры и объединения добавили, но опять полумеры. Ну если вы тырите идеи из Ken C, так и вставляйте целиком.
Но просто печать на экран, обычно, не так востребовано, как печать в строку, а-ля boost::format.
Вот, если бы так просто можно было бы делать печать в std::string — вот это было бы гораздо интереснее.
Думаю,
auto str = sprint(x, y, z);
будет лучше, чем
std::string str = str( boost::format("%f %f %f") % x % y % z );
Ну, в статье речь про Си, а вы про С++, что удивительно, мне казалось, что со всеми наворотами С++ там уже давно не проблема сделать такое. Думаю на С++ можно намного круче сделать, например дать возможность пользователю расширять список принимаемых типов с помощью конструкторов. Но я лет 10 на С++ не смотрел.
#define sprint(x, y, z) (sstream() << x << ' ' << y << ' ' << z).str()
Что выглядит очень уродливо. И тут же возникает вопрос: А как же шаблонная магия, бро?
Может быть, где-то сделано красивее, однако, я окуклился в std и boost и ничего больше не вижу.
P.S.: Честно говоря, я только сейчас заметил в заголовке «Си». Эх, ребята на Си стараются, хотя то же самое уже давно сделано в std или boost. И Си самый распространенный язык… Однако, обратный переход с C++ на Си очень сложный.
Начиная с C++20 в стандартной библиотеке будет std::format. Пока её не дрбавили в популярные реализации, можно пользоваться fmtlib, на основе которой и был сделан std::format.
Вот это боль, я уже на радостях начал в std::format и облом. Когда они уже научатся выпускать стандарт так, чтобы хотя бы в двух компиляторах это поддерживалось?
Иногда думаешь, что как хорошо в питонах и растах с единственной реализацией.
Бро, привет тебе из 2023. В C++23 добавили std::print{ln}. И std::format в С++20.
# С днем рожденья, Саня Илья, успехов тебе, счастья. Выключай. #
Большинство компиляторов Си понимает такую вещь:
int x;
int y = __builtin_types_compatible_p(typeof(x), int);
Большинство — это gcc и примкнувший к нему clang?
github.com/AVI-crak/Rtos_cortex/blob/master/sPrint.h
#define dpr_(X) _Generic((X), \
const char*: soft_print_con, \
char*: soft_print, \
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_nex \
)(X)
Сделал нечто похожее в свое время, ещё натянул на это сверху вариабельные макросы. Прошло время, и я понял всю прелесть стандартного printf с форматами
Посмотрите, как у меня сделано в репозитории, код не раздувается, просто, если сильно упрощёно сказать, вместо a, b, c, d
надо сделать a(b(c(d))))
в итоге генерируется не больше кода чем при обычном вызове printf
а, кажется, даже меньше. Я много игрался с compiler-explorer, чтобы понять, какой компилятор, чего генерирует. Не стал об этом в статье подробно писать, может зря, это, возможно, самая интересная часть. Макросы вызываются рекурсивно, и являются выражениями, и если один из них натыкается на последний аргумент (void)0
он последущие не вызывает, такой currying. В результате, сколько аргументов вы дали, столько кода и сгенерируется, а в вашем случае всегда кладется в стек все 25 аргументов (или сколько там их прописано в макросе).
Посмотрите, как у меня сделано в репозитории, код не раздувается
Стоп, ваша обёртка попадает в бинарник?
_Generic((X) и перечисление аргументов выполняются на уровне перепроцессора, это вообще не попадает в бинаркик. Обёртка из макросов получается большой, тут я согласен. Но думаю что имеет смысл один раз написать, и больше никогда не трогать.
Кстати, макросы не раскрываются даже на нулевом уровне оптимизации, с поставленной галочкой для макросов.
Спасибо за очаровательный макрос!
Вопросы и немножко вкусовщина к автору:
Вы уверены, что нужено выводить пробел между аргументами по умолчанию? Выводить тестовую печать случается по-разному, в том числе и компактно.
Из той же оперы и подсветка. У вас она включена по умолчанию, хотя я бы ожидал обратного поведения.
Хорошо поддерживаются массивы, что приятно! Но я в коде не увидел поддержки массивов чисел с плавающей запятой. Я что-то не понимаю или они действительно не поддерживаются?
Нет проверки на превышение числа аргументов. Может, стоит static_assert или что-то в этом роде?
Праздная мысль: а что с пользовательскими (user-defined) типами? В данной схеме это невозможно, да и _Generic тут не помощник. Но, быть может, у вас есть какие-то идеи?
Еще раз спасибо за интересный код.
Умный print для C