Открыть список
Как стать автором
Обновить

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

и больше не будет предупреждений компилятора, о том, что символ формата после "%" неверного типа.

Чего ж в этом хорошего?

Ну и потом, printf() хороша как раз своим f, то бишь возможностью задавать форматирование вывода (да, недостатки есть, но сейчас не о них). Здесь же такая возможность отсутствует полностью. Зато присутствует принудительная вставка пробелов до и после значений.

Не очень понятна область применения. Хотя… Как компонент какого-нибудь отладочного дампера — вполне может быть. Тут как раз и цвет для разных типов пригодится, наверное.

P.S.
$ gcc -Wall demo.c                                                     ↑
In file included from demo.c:1:0:
demo.c: In function ‘main’:
print.h:153:40: warning: left-hand operand of comma expression has no effect [-Wunu
sed-value]
 #define __print_push(c,size,cont) (cont, *--_p = c | (size << 5))

...
Для отладки — самое то, удобно.

Не скажете данные платформы? Версия компилятора и ОС.

$ cat /etc/*-release
DISTRIB_ID=LinuxMint
DISTRIB_RELEASE=19.3
DISTRIB_CODENAME=tricia
...


$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Чего ж в этом хорошего?

Думать не нужно.
Прикольный костыль, но printf все равно лучше. Хотелось бы только без спецификаторов типа в строке форматирования, но тут без c++ не обойтись
что символ формата после "%" неверного типа.

Просто хочу напомнить, что 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"?

  1. Так сначала и было, но потом возникли идеи с цветом, с печатью char и как символа и как числа, к тому же так любой пользователь может просто открыть интересующий "этаж if/else" и сделать так как ему надо (кто-то любит всё hex).

необходимость печатать массивы?… _Generic() — это C11

  1. В библиотеке над которой я работаю, очень удобно печатать массивы. Подумаю сделать #ifdef на будущее и использовать _Generic там где не обнаружится поддержки builtin-ов.

sql_bind(stmt, foo, 5, «bar», 7)

  1. Однозначно да, биндить к запросу очень было бы удобно. Подумаю, как-нибудь, можно ли генерализовать это в библиотеку, скажем va_arg_generic.

свои типы, с которыми хочется уметь работать, «как с родными».

  1. Вот ещё даже не пробовал совсем ничего такого с user-defined types, алиасами, структурами, работает?
Вот ещё даже не пробовал совсем ничего такого с user-defined types, работает?
С _Generic в GCC точно работает, в clang вроде тоже пробовал, но точно не помню. По идее, с __builtin_types_compatible_p() тоже должно работать.
В итоге должен получится Python c синтаксисом С?

Хорошая, годная магия. Но вот если бы язык развивался, и в коммитете занимались бы делом, то давно можно было бы сделать что-то подобное плюсовой libfmt или питоновскому fmt. Вынести это на уровень компилятора и радоваться.

Пожалуйста, не надо. Комитет Си отлично справляется со своей работой и поддерживает нормальное поступательное развитие того, что действительно нужно.

Зря Вы так, комитет нормально работает, медленно только. Из С можно сделать плюсы, IronPython или какой другой язык, насыпать полный пакет сахара — но всё это испортит язык, как и любое предложение «давайте сделаем как в...» вместо «нужно решить такую-то проблему, вот возможные варианты».

На мой взгляд комитет никак не работает, а начиная с С99 работает только во вред.


Если в чай добавить полный пакет сахара то выходит не очень, а если немного то даже очень. Можно, и даже нужно, в Си добавить немного сахара и для этого не обязательно ломать совместимость и прозрачность.


Это просто жесть какая-то, один из самых используемых языков в мире совсем не развивается.


Комитет — это про лебедь, рак и щуку. У них нет концепции развития языка.
Вот _Generic, сделали сугубо для математической библиотеки.
Нити добавили в стандартную библиотеку и тут же обгадились, что нельзя было pthreads засунуть?
Анонимные структуры и объединения добавили, но опять полумеры. Ну если вы тырите идеи из Ken C, так и вставляйте целиком.

Радуйтесь, сейчас комитет работает над модульностью. Вообще С он как айсберг, в большинстве своём состоит из legacy, даже я со своим небольшим опытом видел работающий код на K&R C (и переписывал его на С99 в связи с заменой оборудования). Туда очень что-то сложно добавить, чтобы олды не заплевали. Кто хочет новизны, берёт подмножество раста или плюсов.
Очень круто, прямо как паскалевский println!
Но просто печать на экран, обычно, не так востребовано, как печать в строку, а-ля boost::format.
Вот, если бы так просто можно было бы делать печать в std::string — вот это было бы гораздо интереснее.
Думаю,
auto str = sprint(x, y, z);
будет лучше, чем
std::string str = str( boost::format("%f %f %f") % x % y % z );

Ну, в статье речь про Си, а вы про С++, что удивительно, мне казалось, что со всеми наворотами С++ там уже давно не проблема сделать такое. Думаю на С++ можно намного круче сделать, например дать возможность пользователю расширять список принимаемых типов с помощью конструкторов. Но я лет 10 на С++ не смотрел.

Ну, на C++ действительно можно просто понаделать макросов вида:
#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 и облом. Когда они уже научатся выпускать стандарт так, чтобы хотя бы в двух компиляторах это поддерживалось?


Иногда думаешь, что как хорошо в питонах и растах с единственной реализацией.

В С++ есть потоки. cout << «preved» << 12.5 << «medved» << 5 << "\n";
Большинство компиляторов Си понимает такую вещь:

int x;
int y = __builtin_types_compatible_p(typeof(x), int);



Большинство — это gcc и примкнувший к нему clang?

Ещё Intel и Tiny.

Написал нечто похожее, у меня называется printo(). Тоже печатает почти всё, ну кроме перечислений — уж очень дико код раздувается.
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) и перечисление аргументов выполняются на уровне перепроцессора, это вообще не попадает в бинаркик. Обёртка из макросов получается большой, тут я согласен. Но думаю что имеет смысл один раз написать, и больше никогда не трогать.
Кстати, макросы не раскрываются даже на нулевом уровне оптимизации, с поставленной галочкой для макросов.

Спасибо за очаровательный макрос!


Вопросы и немножко вкусовщина к автору:


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


  2. Из той же оперы и подсветка. У вас она включена по умолчанию, хотя я бы ожидал обратного поведения.


  3. Хорошо поддерживаются массивы, что приятно! Но я в коде не увидел поддержки массивов чисел с плавающей запятой. Я что-то не понимаю или они действительно не поддерживаются?


  4. Нет проверки на превышение числа аргументов. Может, стоит static_assert или что-то в этом роде?


  5. Праздная мысль: а что с пользовательскими (user-defined) типами? В данной схеме это невозможно, да и _Generic тут не помощник. Но, быть может, у вас есть какие-то идеи?



Еще раз спасибо за интересный код.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.