this->notes.
前往频道在 Telegram
О разработке, архитектуре и C++. Tags: #common, #cpp, #highload и другие можно найти поиском. Задачки: #poll. Мои публикации: #pub. Автор и предложка: @vanyakhodor. GitHub: dasfex.
显示更多4 498
订阅者
-124 小时
-117 天
-3730 天
数据加载中...
相似频道
标签云
进出提及
---
---
---
---
---
---
吸引订阅者
七月 '26
七月 '26
+15
在0个频道中
六月 '26
+53
在2个频道中
Get PRO
五月 '26
+106
在0个频道中
Get PRO
四月 '26
+70
在1个频道中
Get PRO
三月 '26
+57
在2个频道中
Get PRO
二月 '26
+30
在0个频道中
Get PRO
一月 '26
+69
在0个频道中
Get PRO
十二月 '25
+75
在0个频道中
Get PRO
十一月 '25
+75
在1个频道中
Get PRO
十月 '25
+141
在3个频道中
Get PRO
九月 '25
+1 111
在1个频道中
Get PRO
八月 '25
+774
在1个频道中
Get PRO
七月 '25
+342
在3个频道中
Get PRO
六月 '25
+94
在2个频道中
Get PRO
五月 '25
+100
在2个频道中
Get PRO
四月 '25
+71
在1个频道中
Get PRO
三月 '25
+63
在2个频道中
Get PRO
二月 '25
+51
在1个频道中
Get PRO
一月 '25
+34
在0个频道中
Get PRO
十二月 '24
+56
在0个频道中
Get PRO
十一月 '24
+100
在1个频道中
Get PRO
十月 '24
+93
在1个频道中
Get PRO
九月 '24
+220
在2个频道中
Get PRO
八月 '24
+76
在0个频道中
Get PRO
七月 '24
+155
在1个频道中
Get PRO
六月 '24
+44
在1个频道中
Get PRO
五月 '24
+86
在0个频道中
Get PRO
四月 '24
+144
在5个频道中
Get PRO
三月 '24
+100
在1个频道中
Get PRO
二月 '24
+64
在0个频道中
Get PRO
一月 '24
+132
在0个频道中
Get PRO
十二月 '23
+26
在0个频道中
Get PRO
十一月 '23
+16
在0个频道中
Get PRO
十月 '23
+26
在0个频道中
Get PRO
九月 '23
+29
在0个频道中
Get PRO
八月 '23
+29
在0个频道中
Get PRO
七月 '23
+63
在0个频道中
Get PRO
六月 '23
+50
在0个频道中
Get PRO
五月 '23
+53
在0个频道中
Get PRO
四月 '23
+145
在0个频道中
Get PRO
三月 '23
+48
在0个频道中
Get PRO
二月 '23
+46
在0个频道中
Get PRO
一月 '23
+38
在0个频道中
Get PRO
十二月 '22
+33
在0个频道中
Get PRO
十一月 '22
+60
在0个频道中
Get PRO
十月 '22
+37
在0个频道中
Get PRO
九月 '22
+90
在0个频道中
Get PRO
八月 '22
+49
在0个频道中
Get PRO
七月 '22
+352
在0个频道中
| 日期 | 订阅者增长 | 提及 | 频道 | |
| 14 七月 | 0 | |||
| 13 七月 | +2 | |||
| 12 七月 | +1 | |||
| 11 七月 | +2 | |||
| 10 七月 | +2 | |||
| 09 七月 | 0 | |||
| 08 七月 | 0 | |||
| 07 七月 | +1 | |||
| 06 七月 | +1 | |||
| 05 七月 | +3 | |||
| 04 七月 | +1 | |||
| 03 七月 | +1 | |||
| 02 七月 | 0 | |||
| 01 七月 | +1 |
频道帖子
#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 |
