this->notes.
4.49K subscribers
36 photos
1 file
415 links
О разработке, архитектуре и C++.

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

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.
🤯104👍1🤔1
#cpp

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.
10🔥5👍1🤯1🌚1
#cpp

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👍42
#cpp

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.

Давайте представим такой макрос:

#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🥴31
#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.
🤯264👎2🤪2
#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.
👍11🌚6🤯21👎1🤔1
#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.
👍14🫡83😱1🌚1
#cpp

Day 38.

Есть целая отдельная библиотека Boost.Preprocessor, в которой используется довольно много подходов, на которые мы с вами смотрели.

Код там забористый.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🌚7🫡3👍2