ch
Feedback

不要被骗子欺骗!Telemetrio 会找到并标记这些频道 👉 如果想查看标记,请订阅

this->notes.

this->notes.

前往频道在 Telegram

О разработке, архитектуре и C++. Tags: #common, #cpp, #highload и другие можно найти поиском. Задачки: #poll. Мои публикации: #pub. Автор и предложка: @vanyakhodor. GitHub: dasfex.

显示更多
4 492
订阅者
+124 小时
-67 天
-2030 天
帖子存档
#highload Я не очень понял, как это работает. Видосы на самом канале так и не появились, хотя прошло уже больше года с конференции. Плейлист со всеми докладами доступен, и видео выложили год назад. Доступ к ним явно есть. Но просмотров нет. То есть как-то никто и не в курсе, что их можно смотреть. Доклады с Saint HighLoad++ 2025. 0. Эволюция векторного поиска в YDB. Александр Зевайкин. Есть ещё в тексте (правда не уверен, что контент 1 в 1). 1. Технологии распределенной карты для автономного транспорта. Гоша Лутидзе. Прикольное введение в кусок вот этих самоездящих штук. 2. 20 лет на граблях: ошибки, отказы и выводы. Александр Стерлигов. Несколько кайфовых историй про разные инциденты. Я тоже такое сделаю когда-нибудь. 3. Взгляд за кулисы Яндекс Карт: от сырых спутниковых данных до четких изображений. Игорь Орпанен. 4. Четыре девятки хватит всем? Разбираемся с доступностью дата-центров. Алексей Учакин. Доклад про датацентры, как они устроены с точки зрения пользователя, что вам обещают и (не)гарантирует. 5. Почему в космосе (пока) нет дата-центров. Александр Токарев. Ещё один доклад про датацентры, но в космосе. Приятный научпоп. 6. Про избыточность WAL в Postgres. Андрей Бородин. Интересное для понимания устройства WAL в pg. Не то, чтобы я запомнил что-то, но удовольствие получил. Факт, который понравился: в pg есть лагающая реплика. Вы можете сделать реплику, в которой всё применяется с задержкой в час, и при факапе остановить её обновление + переключиться на неё. У меня коллеги когда-то делали похожую штуку, но на уровне приложения. Фиксировали набор табличек, которым нужно делать снапшоты (раз в час и сутки, например). И если источник данных накатил что-то неправильное, из-за чего не получается клиентам отдать данные, мы (как промежуточное звено) могли руками включить чтение из подходяшего снапшота. Пару раз помогло восстановиться на инцидентах за минуты.

#cpp и не только. НАПОМИНАЮ, что 1ого августа будет Back to Back: в Москве back to back трек + C++ трек, в Ереване только back to back, а в Белграде только C++ one. Программу на плюсовый трек мы собрали сочную. Старались, честное слово. Коллеги по цеху на Back to Back тоже интересного выбрали. Меня, дай бог всё будет хорошо, сможете найти в Белграде. Можем с вами за здоровье Страуструпа выпить ракии (хотя я ещё её не пробовал, может и не моё). Буду стараться за неделю после конфы выдать трип репорт. У вас ещё есть время зарегистрироваться!

#cpp Привет. Сегодня последний day 52 марафона. Я знаю, что кому-то он поднадоел. Не зря от меня отписалось больше полусотни человек. Мне тоже надоело, если честно. Если вдруг вы не узнали/не знали, марафон был основан на C++ Preprocessor Iceberg. Я в целом шёл от начала почти по каждому пункту, скипая что-то, что выглядит совсем мутным и невнятным, что я не смог заставить работать или не смог осознать. Попутно старался дополнять своим ресёрчем. Постов фактически вышло на пару десятков меньше, чем пунктов в оригинальном источнике. Мы примерно на половину задели Deep water и совсем не задели The abyss. К концу я уже высох от темы и решил, что душить себя не очень полезно. Лучше чем-то в радость позаниматься. Так что заинтересованные могут доисследовать самый низ айсберга. Расскажите в комментах, как вам. Надо такое делать вообще ещё когда-нибудь? На какие темы? Или нахер и лучше ограничиться обычными форматами? Не факт конечно, что я ваше мнение учту, но мне хочется его знать. Если не хотите писать открытым текстом, то вот варианты quite feedback: 🔥 — круто, можно повторять 🥴 — круто, можно повторять, но слишком долго 🤯 — круто, лучше не повторять 🌚 — не круто, лучше не повторять. Ниже собрал ссылочки для удобства. Пересылайте друзьям, мамам и папам. Пользуйтесь в будущем сами. Above the iceberg: • #include • include guards • #pragma once • #if • заворачивание в скобки • do-while • ARRAY_SIZE и sizeof On the iceberg: • stringification operator • token concatenation operator • __VA_ARGS__ • #undef • #error • comments concatenation • X-macro Below the water: • FILE and LINE • DATE and TIME • #line • триграфы и иже с ними • _Pragma • -E Middle of the iceberg: • COUNTER • CONCAT • #elifdef #elifndef • #embed • SCAN • лимит на макросы • mcpp • перегрузка макросов • default args в макросах Bottom of the iceberg: • blue paint • BOOL, IF • INC/DEC • LOOP • Boost.Preprocessor Below the iceberg: • LIST • ORDER_PP • #assert • #include_next • ppstep • how macro expansion works • sequence Deep water: • итерация по sequence • chaos-pp • Metalang99 • не знаю точно что это • push/pop макросов • ещё несколько ссылочек

#cpp Day 51. Есть ещё несколько интересных репозиториев, связанных с препроцессором: • datatype99 (фактически синтаксический сахар для генерации типов, но с дополнительными плюшками вроде паттерн матчинга) • interface99 (Golang/Rust inspired интерфейсы для C99) • boline (integer arithmetics with base 16). У последнего код с вайбом ASCII-art. И есть ещё подгон от подписчика про расширение в clang, позволяющее делать макросы рекурсивными. Названо оно __THIS_MACRO__:

#define fold_left(op, head, ...)(__VA_OPT__(__THIS_MACRO__(op,__VA_ARGS__)op)head)

fold_left(+, 1, 2, 3, 4) // раскрывается в ((((4)+3)+2)+1)
@thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 50. Макросы можно класть на и доставать из (со) стека:

#define PRAMGA(...) _Pragma(#__VA_ARGS__)
#define POP(m) PRAMGA(pop_macro(#m))

#define X 1 POP(X)
#pragma push_macro("X")
#define X 2 POP(X)
#pragma push_macro("X")
#define X 3 POP(X)
#pragma push_macro("X")
#define X 4 POP(X)
X // 4
X // 3
X // 2
X // 1
Не уверен, что понимаю, зачем это нужно. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 49. Одна из моделей, которую можно реализовать на макросах (хотя мы выяснили, что можно любую видимо) — continuation
#cpp Day 49. Одна из моделей, которую можно реализовать на макросах (хотя мы выяснили, что можно любую видимо) — continuation machine. Фактически это штука, в которой вы можете построить пайплайн работы с данными (но такими словами про неё не говорят, слишком современно). На картинке ниже страшная реализация для простого кейса работы с регистрами (разбирать мы ёе не будем, я хочу просто подышать.....). Кстати репозиторий это что-то вроде оптимизированного интерпретатора Brainfuck. Есть и другие альтернативы: bfi, CPP_COMPLETE. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 48. Если вам нужна сравнительно мощная, но более современная альтернатива chaos-pp, стоит взглянуть на Metalang99. Это хорошая попытка сделать препроцессор похожим на функциональный язык программирования. Но что более важно, проект хорошо документирован. chaos-pp в этом плане проигрывает (и служит скорее исследовательским артефактом). Так что Metalang99 — ваш второй выбор (после Boost.Preprocessor). @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 47. Я уже упоминал Paul Mensodines, который написал огромное письмо про механизм раскрытия макросов. И упоминал про Boost.Preprocessor. Вот Paul когда-то написал chaos-pp — настоящая глыба в мире препроцессорных библиотек. Там вообще всё что хотите. Рекурсия, высокоуровневые алгоритмы (MAP, FOLD, FILTER, ZIP и др.), лямбды и частичное применение макросов, разные контейнеры (sequence, tuple, list). Правда её не так часто используют. Просто потому что она очень сложная (== высокий порог входа), без документации, а большинство задач решаются чем-то попроще. Но как артефакт кайфово. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 46. Шагаем на ступеньку номер 7. Мы бы могли с вами реализовать итерацию по sequence, но иногда лучше вовремя остановиться и заиспользовать что-то готовое:

#define PRINT(r, data, elem) elem

BOOST_PP_SEQ_FOR_EACH(
    PRINT,
    _,
    (a)(b)(c)(d)
)
Получим

a b c d
Как это реализовано? Ну вот примерно как вчера откусываете по одному токену из начала и применяете к нему «функтор». Чего непонятно-то??? @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 45. Мы уже обсуждали, как сделать LIST, но есть и более свойственная препроцессору структура данных: sequence. Sequence это просто набор элементов в скобках:
(1)(2)(3)(4)(5)
Тут 5 элементов. Но конечно мы не ограничиваемся числами:
(a)(b)(c)
(int)(double)(bool)
(1)(2)()((),w,())(awoo())(())
Основные операции иногда реализуются просто:

#define SEQ_PUSH_FRONT(x, seq) (x) seq
SEQ_PUSH_FRONT(z, (a)(b)(c)) // (z)(a)(b)(c)

#define FRONT(seq) FRONT_I seq
#define FRONT_I(x) x
FRONT((a)(b)(c)) 
// FRONT_I(a)(b)(c)
// a
В конце на самом деле надо ещё как-то убрать (b)(c), но сейчас это не так важно. Другие операции могут быть чуть сложнее, но концептуально там ничего такого. В любом случае самим реализовывать вам не нужно. Берите Boost Preprocessor и крутите

BOOST_PP_SEQ_SIZE((a)(b)(c)) // 3
BOOST_PP_SEQ_ELEM(1, (a)(b)(c)) // b
BOOST_PP_SEQ_PUSH_BACK(seq, x)
Там ещё и итерироваться можно, но про это завтра. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 44. Сегодня ссылка, где Paul Mensodines скрупулёзно поясняет механизм раскрытия макросов: How macro expansion works. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 43. Так как писать код на препроцессоре бывает неимоверно неприятно, есть тулы в помощь. Например, ppstep, который позволяет интерактивно (пошагово) разбираться в раскрытии ваших макросов и всего вокруг. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 42. Представьте такую ситуацию: Вы мегаумный программист, разрабатывающий инфру в какой-нибудь компании. И Вы хорошо понимаете, какие ошибки совершают ваши коллеги чаще других. Вы бы хотели оградить коллег от частых ошибок. Например, запретить пользоваться какой-то функцией из стандартной библиотеки и сообщить в ошибке альтернативу. Или может вы пишете какой-то тулинг для сбора статистики и хотите подменить стандартную функцию своей, которая будет сначала собирать какую-то информацию, а потом как обычно вызывать стандартное решение. Для решения таких задач можно подменить стандартный хедер на свой. Для начала его надо реализовать:

// my_headers/string.h

#pragma once

#include <string.h> // подключаем системный хедер

#define strcpy(...) static_assert(false, "Use strncpy instead")
И начать компилировать вашу программу как-нибудь так:

clang++ -I my_headers ...
(тут мы указываем путь к хедерам, так как наш my_headers расположен первым, то и проверяться он будет первым) Или через cmake как-то так:
target_include_directories(
    my_target
    BEFORE PRIVATE my_headers
)
Теперь пользователь будет писать

#include <string.h>
и вместо системного хедера подключится ваш. Круто? А видите ли вы какую-нибудь проблему в реализации my_headers/string.h? Например, что внутри мы тоже подключаем <string.h>, который (согласно нашей сборке проекта) будет искаться в my_headers/string.h. Какая-то рекурсия! Для решения этой проблемы некоторые компиляторы реализовали нестандартное расширение #include_next. Достаточно в нашем хедере использовать именно его:

#include_next <string.h>
#include_next будет инклудить указанный файл, но поиск файла будет осуществляться после того каталога, в котором находится текущий файл. То есть в нашем примере мы сразу пропустим директорию my_headers и начнём искать где-то ещё. Если у вас нет других string.h до системных хедеров, мы получим нужный файл из стандартной библиотеки. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 41. Кроме хорошо знакомого вам assert был и ещё никак не связанный с первым #assert. Подразумевалось, что #assert будет помогать генерировать какие-то утверждения для препроцессора. Например

#assert cpu(x86)
#assert os(linux)
#assert this(notes)
после чего вы могли писать

#if #cpu(x86) // true
...
#if #os(linux) // true
...
#if #this(notes) // true
...
#if #os(win) // false
...
То есть с помощью #assert препроцессор записывал в отдельную табличку верные утверждения, а потом умел сообщать, есть ли такой факт. Причём проверка проводилась для predicate(argument). Ещё утверждения можно было отменять с помощью

#unassert cpu(x86)
И больше это неправда. Фактически это примитивная логическая база знаний. Выше я писал «был», потому что в C11 #assert и #unassert не стало. Потому что фактически почти никто не реализовал. Все писали примерно:

#define CPU_X86 1

#if CPU_X86
#ifdef CPU_X86
@thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 40. Представьте, что вы хотите на препроцессоре писать не макросами, а на каком-нибудь Lisp-like языке программирования (ну просто попытайтесь представить, что вы этого хотите). Есть такая библиотека ORDER_PP:

ORDER_PP(8to_lit(8add(8, 5))) // 15
или

ORDER_PP(
  8to_lit(
    8mul(
      8add(2,3),
      8add(4,5)
    )
  )
) // 45
Язык на самом деле мощнее. Можно объявлять переменные, писать функции, рекурсию, списками пользоваться, ленивые вычисления мутить (вроде как). Можем вот факториал вычислить (это всё будет вычислено препроцессором!):

ORDER_PP
(
(8define FACT
  (8fn (8N)
    (8if (8zero 8N)
         1
         (8mul 8N
               (FACT (8sub 8N 1)))))
))

ORDER_PP(8to_lit (FACT 6))
(возможно оно не скомпилируется, но суть такая) Как это работает? И что это за 8 перед командами? Я не буду рассказывать про устройство библиотеки, но расскажу основной механизм. Во время лексического анализа препроцессор не знает ни про какие числа. Он знает про токены языка. А они бывают разные:
identifier
pp-number
string-literal
punctuator
... and others
Нас интересует pp-number. Ниже примеры того, что является pp-number:
123
1e10
0xFF
3.14
1E+10
0x123ABC

1abc
123hello
8X
8let
8mul
0foobar
И пусть вторую пачку компилятор не сможет разобрать как литералы, это всё равно pp-number (данные, а не identifier). А макросы ищутся только среди идентификаторов. Поэтому библиотека пользуется такими pp-number сколько нужно и не боится, что препроцессор раскроет конструкции раньше времени (8 для префикса автор выбрал по личной симпатии видимо). ORDER_PP пользуется этой возможностью хранить программу как данные и где-то внутри библиотеки в нужный момент происходит склейка pp-numbers в полноценные макросы:

ORDER_PP_8mul // for example
После этого перехода в мир стандартных макросов уже начинается жоское вычисление всего вокруг. Чего только не напридумывают. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 39. Начинаем уровень 6. С помощью макросов можно сделать и какие-то структуры данных (перенося подход всё то же самое можно и в некоторых функциональных языках, и на шаблонах). Давайте реализуем list:

#define LIST_HEAD(a,b) a
#define LIST_TAIL(a,b) b

LIST_HEAD(1,(2,(3,))) // 1
LIST_TAIL(1,(2,(3,))) // (2,(3,))
И на самом деле этого достаточно. Можно научиться проверять на конец:

#define TUPLE_AT_1(x,y,...) y
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
#define LIST_END(...) ,0
#define LIST_IS_END(x) CHECK(LIST_END x,1)

LIST_IS_END((9,))          // 0
LIST_IS_END(LIST_TAIL(9,)) // 1
Теперь вспомним пост про реализацию BOOL и IF и поймём, что это можно использовать как условие в тех конструкциях. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 38. Есть целая отдельная библиотека Boost.Preprocessor, в которой используется довольно много подходов, на которые мы с вами смотрели. Код там забористый. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 37. Иногда можно хулиганить. Как???? Сейчас расскажу. Давайте возьмём такой странный пример:

#define SCAN(...) __VA_ARGS__
#define EMPTY()
#define LOOP_INDIRECTION() LOOP
#define LOOP(x) x LOOP_INDIRECTION EMPTY()() (x)
SCAN вы знаете. EMPTY() раскрывается в пустоту. А два последних понятно во что раскрываются. Там прям справа написано. Но как это работает, мы сейчас рассмотрим. Давайте сначала поймём, во что раскроется LOOP(1):

LOOP(1)
// 1 LOOP_INDIRECTION EMPTY()() (1)
// 1 LOOP_INDIRECTION EMPTY()() (1) // раскрываем EMPTY()
// 1 LOOP_INDIRECTION () (1)
Вот тут важный момент: между LOOP_INDIRECTION и () есть пробел. А значит препроцессор воспринимает это как два отдельных токена. из-за чего раскрывать LOOP_INDIRECTION() он не станет. Так что мы на этом останавливаемся. Теперь давайте просканируем:

SCAN(LOOP(1))
// SCAN(1  LOOP_INDIRECTION () (1))
// 1 LOOP_INDIRECTION()(1) // токены рассматриваются как один поток
// 1 1 LOOP_INDIRECTION () (1)
То есть получили уже 2 единицы в начале. Обобщая идею можно реализовать EVAL:

#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) __VA_ARGS__
И для

EVAL(LOOP(1));
мы получим единицы на всю глубину EVAL:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 LOOP_INDIRECTION ()(1)
Можно в итоге написать макрос, которые из

E3(LOOP(f,1,2,3,4,5,6,7,8,9,END))
раскроется в

// f(1) f(2) f(3) f(4) f(5) f(6) f(7) f(8) f(9)
@thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 36. Иногда используют похожую структуру для реализации INC/DEC:

#define AT_0(a,b,c,d,e,f,g,h) a
#define AT_1(a,b,c,d,e,f,g,h) b
#define AT_2(a,b,c,d,e,f,g,h) c
#define AT_3(a,b,c,d,e,f,g,h) d
#define AT_4(a,b,c,d,e,f,g,h) e
#define AT_5(a,b,c,d,e,f,g,h) f
#define AT_6(a,b,c,d,e,f,g,h) g
#define AT_7(a,b,c,d,e,f,g,h) h

#define CAT(a,b) CAT_I(a,b)
#define CAT_I(a,b) a##b

#define INC_(n) CAT(AT_, n)(1,2,3,4,5,6,7,0)
#define DEC_(n) CAT(AT_, n)(7,0,1,2,3,4,5,6)

#define INC(n) INC_(n)
#define DEC(n) DEC_(n)

INC(6) // 7
INC(7) // 0
DEC(0) // 7
INC(DEC(INC(INC(1)))) // 3
AT_x равносильно взять элемент по индексу x, а INC/DEC просто передают сдвинутое на 1 в нужную сторону количество аргументов в нужном порядке. Тут фактически мы сделали конечную группу \Z_8. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.

#cpp Day 35. Как мы уже поняли, препроцессор умеет только несколько базовых операций: • раскрывать макросы • склеивать токены (##) • работать с запятыми в списках аргументов (то бишь разделять по запятым аргументы). Но иногда хочется делать что-то совсем человеческое. Например, написать if:

IF(1)(a, b)
IF(0)(a, b)
Так нельзя. Потому приходится крутиться. Сегодня придётся напрячь думалку. Зайдём издалека. Давайте определим вот такой странный макрос:

#define PROBE ,found
Обратите внимание, что он раскрывается в 2 аргумента для другого макроса: "" и "found". И сделаем макрос CHECK:

#define TUPLE_AT_1(b,a,...) a // just return 2nd argument
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
Теперь мы можем сделать конструкцию, которая позволит нам узнать, определён ли макрос:

#define FOO PROBE

#define IS_FOO_IMPL(x) IS_FOO_##x 
#define IS_FOO(x) CHECK(IS_FOO_IMPL(x), 0)

#define IS_FOO_FOO PROBE
Теперь можем проверять:

IS_FOO(FOO) 
// CHECK(IS_FOO_IMPL(FOO), 0)
// TUPLE_AT_1(IS_FOO_IMPL(FOO), 0, )
// TUPLE_AT_1(IS_FOO_FOO, 0, )
// TUPLE_AT_1(PROBE, 0, )
// TUPLE_AT_1(, found, 0, )
// found
или иначе:

IS_FOO(BAR)
// ...
// 0
Вроде схема работы понятна, но зачем это нужно, не совсем. Давайте вернёмся к изначальному примеру.

IF(1)(A, B )  // A
IF(0)(A, B )  // B
Сделаем базу:

#define PROBE ~, 1

#define SECOND(a, b, ...) b

#define CHECK(...) SECOND(__VA_ARGS__, 0)
Тогда

CHECK(PROBE) // 1
CHECK(x)       // 0
Теперь построим макрос NOT:

#define NOT_0 PROBE
#define NOT(x) CHECK(NOT_##x)
И когда используем:

NOT(0) // CHECK(NOT_0) -> CHECK(PROBE) -> 1
NOT(1) // CHECK(NOT_1) -> NOT_1 doesn't exist -> 0
Вот мы уже научились инвертировать значение. Осталось сделать BOOL:

BOOL(0) = 0
BOOL(1) = 1
Реализация тривиальная:

#define BOOL(x) NOT(NOT(x))
Последнее, что нам нужно, это сделать IF:

#define IF_0(t, f) f
#define IF_1(t, f) t

IF_0(A, B) // B
IF_1(A, B) // A
Его мы можем собрать через знакомый нам CAT:

#define CAT(a, b) CAT_I(a, b)
#define CAT_I(a, b) a##b

#define IF(c) CAT(IF_, BOOL(c))
Конечно, в коде не обязательно писать IF(0)/IF(1). Вместо значения может быть какой-то другой макрос, который раскроется в конкретное число. Сам макрос может прийти откуда-то снаружи. Жоска мне кажется. Чего только не придумают. @thisnotes. Patreon, Boosty. Спасибо Artyom Garkavy и niki4smirn.