#cpp
Day 29.
В макросе
В общем случае идиома выглядит примерно как макрос
Давайте посмотрим на примере таких макросов:
Получим разное:
Решение обобщается. Во всяких сложных препроцессорных монстрах можно найти
Таким образом вы можете делать в каком-то смысле рекурсию на макросах, хотя она не предусмотрена. Ну это и не рекурсия конечно. Просто много шагов вглубь делаем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 29.
В макросе
CONCAT мы специально делали два уровня, потому что иначе некоторые аргументы (которые тоже могут быть макросами) не раскроются. В общем случае идиома выглядит примерно как макрос
SCAN:
#define SCAN(...) __VA_ARGS__
#define SCAN2(...) SCAN(__VA_ARGS__)
Давайте посмотрим на примере таких макросов:
#define STR(x) #x
#define A(x) (x+x)
#define B(x) (x+x)
Получим разное:
STR A(1) // STR (1+1)
SCAN(STR A(1)) // "(1+1)"
SCAN(STR B A(1)) // STR (1+1+1+1)
SCAN2(STR B A(1)) // "(1+1+1+1)"
Решение обобщается. Во всяких сложных препроцессорных монстрах можно найти
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
...
Таким образом вы можете делать в каком-то смысле рекурсию на макросах, хотя она не предусмотрена. Ну это и не рекурсия конечно. Просто много шагов вглубь делаем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🤯10✍4👍1🤔1
#cpp
Day 30.
Стандарт C99 требует поддерживать 4095 одновременно определённых макросов (не меньше). Это не звучит как глобальная проблема.
Также он требует поддерживать 4095 символов в одной логической строке кода. То есть лимит на размер пользовательского кода для определения макроса — 4085 символов (предполагая, что вам нужно "
Аналогичные лимиты и в C11, и в C2x.
Для C++ текущее значение 65536.
Когда-нибудь сделаю пост про другие лимиты компиляторов.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 30.
Стандарт C99 требует поддерживать 4095 одновременно определённых макросов (не меньше). Это не звучит как глобальная проблема.
Также он требует поддерживать 4095 символов в одной логической строке кода. То есть лимит на размер пользовательского кода для определения макроса — 4085 символов (предполагая, что вам нужно "
#define A ").Аналогичные лимиты и в C11, и в C2x.
Для C++ текущее значение 65536.
Когда-нибудь сделаю пост про другие лимиты компиляторов.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
😁15👍5🤔3🤬2🔥1🤯1🫡1
#cpp
Day 31.
У каждого из крупных компиляторов есть свой препроцессор. Они в целом соответствуют стандарту, но могут расходиться в разных крайних случаях.
А есть mcpp — имплементация препроцессора, максимально точно соответствующего стандарту (С90, С99 и C++98). С его помощью можно было выяснять, как «правильно» себя должен вести препроцессор в вашем компиляторе.
Но что более важно, mcpp предоставляет validation suite — огромный набор тестов для препроцессора.
Можете позалипать в код, если хотите вникнуть в совсем упоротые случаи использования препроцессора для разных задач.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 31.
У каждого из крупных компиляторов есть свой препроцессор. Они в целом соответствуют стандарту, но могут расходиться в разных крайних случаях.
А есть mcpp — имплементация препроцессора, максимально точно соответствующего стандарту (С90, С99 и C++98). С его помощью можно было выяснять, как «правильно» себя должен вести препроцессор в вашем компиляторе.
Но что более важно, mcpp предоставляет validation suite — огромный набор тестов для препроцессора.
Можете позалипать в код, если хотите вникнуть в совсем упоротые случаи использования препроцессора для разных задач.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
❤10🔥5👍1🤯1🌚1
#cpp
Day 32.
C-разработчики тоже люди. Им тоже хочется уметь что-нибудь перегружать. Макросы, например.
Язык сам по себе такой возможности не даёт. Но мы можем сами сделать перегрузку макросов по количеству аргументов (по типу, очевидно, смысла не имеет, так как препроцессор про ваши типы не в курсе. Он с токенами работает):
Красиво блин.
Можно тут посмотреть несколько примеров: https://quuxplusone.github.io/blog/2026/04/02/macro-overloading/
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 32.
C-разработчики тоже люди. Им тоже хочется уметь что-нибудь перегружать. Макросы, например.
Язык сам по себе такой возможности не даёт. Но мы можем сами сделать перегрузку макросов по количеству аргументов (по типу, очевидно, смысла не имеет, так как препроцессор про ваши типы не в курсе. Он с токенами работает):
#define GET_MACRO(_1,_2,_3,x,...) x
#define FOO(...) GET_MACRO(__VA_ARGS__,FOO3,FOO2,FOO1)(__VA_ARGS__)
FOO(1) // FOO1(1)
FOO(1,2) // FOO2(1,2)
FOO(1,2,3) // FOO3(1,2,3)
Красиво блин.
Можно тут посмотреть несколько примеров: https://quuxplusone.github.io/blog/2026/04/02/macro-overloading/
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🤯19🥴5👍4❤2
#cpp
Day 33.
Похожим на вчерашний подход трюком мы можем сделать обёртку над функцией, позволяющую нам добавить значения по умолчанию:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 33.
Похожим на вчерашний подход трюком мы можем сделать обёртку над функцией, позволяющую нам добавить значения по умолчанию:
void foo(int a, int b, float c); // no default args
#define GET_ARGS(_1,_2,_3,x,...) x
#define foo(...) GET_ARGS(__VA_ARGS__, \
foo(__VA_ARGS__), \
foo(__VA_ARGS__,3), \
foo(__VA_ARGS__,2,3),)
foo(1,2,3) // foo(1,2,3)
foo(1,2) // foo(1,2,3)
foo(1) // foo(1,2,3)
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🤩16🤪4
#cpp
Day 34.
Сегодня ступаем на уровень 5.
Есть такой неофициальный термин, который используется для обозначения токенов в процессе раскрытия макросов препроцессором: blue paint.
Давайте представим такой макрос:
1. Встречаем
2. Добавляем
3. Заменяем на
4. Видим снова
5. Но он уже painted blue -> не раскрываем его снова.
Потому получаем
Почему blue?
Насколько я понимаю, в ранних описаниях компиляторов токены буквально красили в разные цвета, что и привело к закреплению такого неофициального термина.
Хотя в стандарте это написано так
https://eel.is/c++draft/cpp.replace#cpp.rescan-3
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 34.
Сегодня ступаем на уровень 5.
Есть такой неофициальный термин, который используется для обозначения токенов в процессе раскрытия макросов препроцессором: blue paint.
Давайте представим такой макрос:
#define A A + 1
A
1. Встречаем
A.2. Добавляем
A в active expansion set (или иначе красим в синий). 3. Заменяем на
A + 1.4. Видим снова
A.5. Но он уже painted blue -> не раскрываем его снова.
Потому получаем
A + 1, а не бесконечную рекурсию. Почему blue?
Насколько я понимаю, в ранних описаниях компиляторов токены буквально красили в разные цвета, что и привело к закреплению такого неофициального термина.
Хотя в стандарте это написано так
If the name of the macro being replaced is found during this scan of the replacement list ..., it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. ...
https://eel.is/c++draft/cpp.replace#cpp.rescan-3
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍14🥴3❤1
#cpp
Day 35.
Как мы уже поняли, препроцессор умеет только несколько базовых операций:
• раскрывать макросы
• склеивать токены (
• работать с запятыми в списках аргументов (то бишь разделять по запятым аргументы).
Но иногда хочется делать что-то совсем человеческое. Например, написать if:
Так нельзя. Потому приходится крутиться.
Сегодня придётся напрячь думалку.
Зайдём издалека.
Давайте определим вот такой странный макрос:
Обратите внимание, что он раскрывается в 2 аргумента для другого макроса: "" и "found".
И сделаем макрос
Теперь мы можем сделать конструкцию, которая позволит нам узнать, определён ли макрос:
Теперь можем проверять:
или иначе:
Вроде схема работы понятна, но зачем это нужно, не совсем. Давайте вернёмся к изначальному примеру.
Сделаем базу:
Тогда
Теперь построим макрос
И когда используем:
Вот мы уже научились инвертировать значение.
Осталось сделать
Реализация тривиальная:
Последнее, что нам нужно, это сделать
Его мы можем собрать через знакомый нам
Конечно, в коде не обязательно писать
Жоска мне кажется. Чего только не придумают.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
🤯26❤4👎2🤪2
#cpp
Day 36.
Иногда используют похожую структуру для реализации
Тут фактически мы сделали конечную группу \Z_8.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍11🌚6🤯2❤1👎1🤔1
#cpp
Day 37.
Иногда можно хулиганить.
Как????
Сейчас расскажу.
Давайте возьмём такой странный пример:
Давайте сначала поймём, во что раскроется
Вот тут важный момент: между
Теперь давайте просканируем:
То есть получили уже 2 единицы в начале.
Обобщая идею можно реализовать
И для
мы получим единицы на всю глубину EVAL:
Можно в итоге написать макрос, которые из
раскроется в
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍14🫡8❤3😱1🌚1
#cpp
Day 38.
Есть целая отдельная библиотека Boost.Preprocessor, в которой используется довольно много подходов, на которые мы с вами смотрели.
Код там забористый.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 38.
Есть целая отдельная библиотека Boost.Preprocessor, в которой используется довольно много подходов, на которые мы с вами смотрели.
Код там забористый.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🌚7🫡3👍2