this->notes.
Ir al canal en Telegram
О разработке, архитектуре и C++. Tags: #common, #cpp, #highload и другие можно найти поиском. Задачки: #poll. Мои публикации: #pub. Автор и предложка: @vanyakhodor. GitHub: dasfex.
Mostrar más4 498
Suscriptores
-124 horas
-117 días
-3730 días
Carga de datos en curso...
Canales Similares
Nube de Etiquetas
Menciones Entrantes y Salientes
---
---
---
---
---
---
Atraer Suscriptores
julio '26
julio '26
+15
en 0 canales
junio '26
+53
en 2 canales
Get PRO
mayo '26
+106
en 0 canales
Get PRO
abril '26
+70
en 1 canales
Get PRO
marzo '26
+57
en 2 canales
Get PRO
febrero '26
+30
en 0 canales
Get PRO
enero '26
+69
en 0 canales
Get PRO
diciembre '25
+75
en 0 canales
Get PRO
noviembre '25
+75
en 1 canales
Get PRO
octubre '25
+141
en 3 canales
Get PRO
septiembre '25
+1 111
en 1 canales
Get PRO
agosto '25
+774
en 1 canales
Get PRO
julio '25
+342
en 3 canales
Get PRO
junio '25
+94
en 2 canales
Get PRO
mayo '25
+100
en 2 canales
Get PRO
abril '25
+71
en 1 canales
Get PRO
marzo '25
+63
en 2 canales
Get PRO
febrero '25
+51
en 1 canales
Get PRO
enero '25
+34
en 0 canales
Get PRO
diciembre '24
+56
en 0 canales
Get PRO
noviembre '24
+100
en 1 canales
Get PRO
octubre '24
+93
en 1 canales
Get PRO
septiembre '24
+220
en 2 canales
Get PRO
agosto '24
+76
en 0 canales
Get PRO
julio '24
+155
en 1 canales
Get PRO
junio '24
+44
en 1 canales
Get PRO
mayo '24
+86
en 0 canales
Get PRO
abril '24
+144
en 5 canales
Get PRO
marzo '24
+100
en 1 canales
Get PRO
febrero '24
+64
en 0 canales
Get PRO
enero '24
+132
en 0 canales
Get PRO
diciembre '23
+26
en 0 canales
Get PRO
noviembre '23
+16
en 0 canales
Get PRO
octubre '23
+26
en 0 canales
Get PRO
septiembre '23
+29
en 0 canales
Get PRO
agosto '23
+29
en 0 canales
Get PRO
julio '23
+63
en 0 canales
Get PRO
junio '23
+50
en 0 canales
Get PRO
mayo '23
+53
en 0 canales
Get PRO
abril '23
+145
en 0 canales
Get PRO
marzo '23
+48
en 0 canales
Get PRO
febrero '23
+46
en 0 canales
Get PRO
enero '23
+38
en 0 canales
Get PRO
diciembre '22
+33
en 0 canales
Get PRO
noviembre '22
+60
en 0 canales
Get PRO
octubre '22
+37
en 0 canales
Get PRO
septiembre '22
+90
en 0 canales
Get PRO
agosto '22
+49
en 0 canales
Get PRO
julio '22
+352
en 0 canales
| Fecha | Crecimiento de Suscriptores | Menciones | Canales | |
| 14 julio | 0 | |||
| 13 julio | +2 | |||
| 12 julio | +1 | |||
| 11 julio | +2 | |||
| 10 julio | +2 | |||
| 09 julio | 0 | |||
| 08 julio | 0 | |||
| 07 julio | +1 | |||
| 06 julio | +1 | |||
| 05 julio | +3 | |||
| 04 julio | +1 | |||
| 03 julio | +1 | |||
| 02 julio | 0 | |||
| 01 julio | +1 |
Publicaciones del Canal
#cpp
Day 43.
Так как писать код на препроцессоре бывает неимоверно неприятно, есть тулы в помощь. Например, ppstep, который позволяет интерактивно (пошагово) разбираться в раскрытии ваших макросов и всего вокруг.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
| 2 | #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. | 916 |
| 3 | #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. | 1 000 |
| 4 | #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. | 1 014 |
| 5 | #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. | 1 151 |
| 6 | #cpp
Day 38.
Есть целая отдельная библиотека Boost.Preprocessor, в которой используется довольно много подходов, на которые мы с вами смотрели.
Код там забористый.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 309 |
| 7 | #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. | 1 442 |
| 8 | #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. | 1 332 |
| 9 | #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. | 1 314 |
| 10 | #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. | 1 388 |
| 11 | #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. | 1 460 |
| 12 | #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. | 1 641 |
| 13 | #cpp
Day 31.
У каждого из крупных компиляторов есть свой препроцессор. Они в целом соответствуют стандарту, но могут расходиться в разных крайних случаях.
А есть mcpp — имплементация препроцессора, максимально точно соответствующего стандарту (С90, С99 и C++98). С его помощью можно было не только выяснять, как «правильно» себя должен вести препроцессор в вашем компиляторе.
Но что более важно, mcpp предоставляет validation suite — огромный набор тестов для препроцессора.
Можете позалипать в код, если хотите вникнуть в совсем упоротые случаи использования препроцессора для разных задач.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 972 |
| 14 | #cpp
Day 30.
Стандарт C99 требует поддерживать 4095 одновременно определённых макросов (не меньше). Это не звучит как глобальная проблема.
Также он требует поддерживать 4095 символов в одной логической строке кода. То есть лимит на размер пользовательского кода для определения макроса — 4085 символов (предполагая, что вам нужно "#define A ").
Аналогичные лимиты и в C11, и в C2x.
Для C++ текущее значение 65536.
Когда-нибудь сделаю пост про другие лимиты компиляторов.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 073 |
| 15 | #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. | 2 122 |
| 16 | #cpp
Day 28.
Давайте представим, что вы разрабатываете какое-нибудь десктопное приложение. У вас есть примерно такой набор файлов:
app.exe
logo.png
config.json
shader.spv
В коде вы работаете с файлами примерно так:
std::ifstream f("config.json");
Какие проблемы? Я вижу такие:
• файл могут забыть положить рядом
• файл могут удалить
• файл могут подменить
• распространять программу неудобно.
В идеале получить просто app.exe.
Достичь этого мы можем с помощью встраивания байтов прямо в исполняемый файл.
Есть несколько вариантов, как это сделать.
Например
xxd -i bytes.bin > bytes.h
С помощью утилиты xxd и флага -i (указывающего сгенерировать C-код). И далее простой #include "bytes.h".
Другие способы:
• objcopy
• inline asm с .incbin
• генерация кода из бинарника любыми другими способами.
Проблема как минимум xxd в том, что при изменении бинарного файла придётся перезапустить генерацию хедера.
Все от этих костылей устали и придумали #embed (C23):
constexpr unsigned char data[] = {
#embed "file.bin"
};
Всё. Одна директива. Изменения не теряем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 872 |
| 17 | #cpp
Day 27.
В C23 подъехали новые директивы #elifdef #elifndef.
Зачем они нужны?
Чтобы вот такой код:
#ifdef __clang__
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#elif defined(_MSC_VER)
#define COMPILER "msvc"
#else
#define COMPILER "unknown"
#endif
выглядел вот так:
#ifdef __clang__
#define COMPILER "clang"
#elifdef __GNUC__
#define COMPILER "gcc"
#elifdef _MSC_VER
#define COMPILER "msvc"
#else
#define COMPILER "unknown"
#endif
Фактически пытаемся снизить шум от defined(...).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 848 |
| 18 | #cpp
Day 26.
Популярной идиомой, которую мы уже использовали для получения переменных с уникальными именами, является макрос CONCAT:
#define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b)
Причём важно иметь прокси реализацию, так как тогда ваш макрос:
#define CONCAT_IMPL(a, b) a##b
будет работать не так, как ожидается:
#define foo FOO
#define bar BAR
#define FOOBAR ~
CONCAT_IMPL(foo,bar) // foobar
CONCAT(foo,bar) // ~
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 965 |
| 19 | #cpp
Day 25.
Мы это краем уха задевали, но давайте ещё раз проговорим.
Макросы живут в глобальном (как бы упрощено и неправильно это ни звучало) неймспейсе, так что с ними надо быть осторожными.
Базовая рекомендация: приписывать в начало макроса имя библиотеки: THISNOTESLIB_.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 967 |
| 20 | #cpp
Day 24.
Делам шаг на уровень 4.
Большинство компиляторов дают возможность использовать __COUNTER__ (расширение компиляторов, не стандарт).
В первый вызов он отдаёт 0, а далее значение на 1 больше.
Счётчик один внутри translation unit.
Если помните, мы генерировали уникальные имена для переменных с помощью LINE, но с ним проблема в том, что вы не можете объявить две переменные в одной строке, так как у обеих будет одно и то же имя. COUNTER решает эту проблему. Как его ни вызывай, он всё равно вверх растёт:
#define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__)
int UNIQUE_NAME(x), UNIQUE_NAME(x);
Получим что-то вроде
int x0, x1;
Зачем нужны вообще эти уникальные имена? Например, для defer/finally/scope_exit:
#define SCOPE_EXIT(code) \
auto UNIQUE_NAME(_scope_) = make_scope_exit([&] { code; });
Вроде очевидно, но можно и такое сделать:
#define FOO(x) x + x
FOO(__COUNTER__); // 0 + 1
Всякими хитрыми трюками можно его «сбрасывать», но про это почекайте сами.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 173 |
