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 38.

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

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

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🌚7🫡3👍2
#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.
👍9👎4🗿2🤯1🤓1
#cpp

Day 40.

Представьте, что вы хотите на препроцессоре писать не макросами, а на каком-нибудь Lisp-like языке программирования (ну просто попытайтесь представить, что вы этого хотите).

Есть такая библиотека ORDER_PP:

ORDER_PP(8to_lit(8add(8, 5))) // 13

или

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.
🤯20👍4🔥1
#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.
9🔥1
#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.
👍20🔥53🤯2🌚1
#cpp

Day 43.

Так как писать код на препроцессоре бывает неимоверно неприятно, есть тулы в помощь. Например, ppstep, который позволяет интерактивно (пошагово) разбираться в раскрытии ваших макросов и всего вокруг.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
😁6🤩4👍3
#cpp

Day 44.

Сегодня ссылка, где Paul Mensodines скрупулёзно поясняет механизм раскрытия макросов: How macro expansion works.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍9
#cpp

Day 45.

Мы уже обсуждали, как сделать LIST, но есть и более свойственная препроцессору структура данных: sequence.

Sequence это просто набор элементов в скобках:

(1)(2)(3)(4)(5)

Тут 5 элементов. Но конечно мы не ограничиваемся числами:

(a)(b)(c)
(int)(double)(bool)
(1)(2)()((),w,())(awoo())(())

Основные операции иногда реализуются просто:

#define SEQ_PUSH_FRONT(x, seq) (x) seq
SEQ_PUSH_FRONT(z, (a)(b)(c)) // (z)(a)(b)(c)

#define FRONT(seq) FRONT_I seq
#define FRONT_I(x) x
FRONT((a)(b)(c))
// FRONT_I(a)(b)(c)
// a

В конце на самом деле надо ещё как-то убрать (b)(c), но сейчас это не так важно.

Другие операции могут быть чуть сложнее, но концептуально там ничего такого. В любом случае самим реализовывать вам не нужно. Берите Boost Preprocessor и крутите

BOOST_PP_SEQ_SIZE((a)(b)(c)) // 3
BOOST_PP_SEQ_ELEM(1, (a)(b)(c)) // b
BOOST_PP_SEQ_PUSH_BACK(seq, x)

Там ещё и итерироваться можно, но про это завтра.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍5🤯2🤓1
#cpp

Day 46.

Шагаем на ступеньку номер 7.

Мы бы могли с вами реализовать итерацию по sequence, но иногда лучше вовремя остановиться и заиспользовать что-то готовое:

#define PRINT(r, data, elem) elem

BOOST_PP_SEQ_FOR_EACH(
PRINT,
_,
(a)(b)(c)(d)
)

Получим

a b c d


Как это реализовано?

Ну вот примерно как вчера откусываете по одному токену из начала и применяете к нему «функтор». Чего непонятно-то???

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍8🗿3👎1
#cpp

Day 47.

Я уже упоминал Paul Mensodines, который написал огромное письмо про механизм раскрытия макросов.

И упоминал про Boost.Preprocessor.

Вот Paul когда-то написал chaos-pp — настоящая глыба в мире препроцессорных библиотек. Там вообще всё что хотите. Рекурсия, высокоуровневые алгоритмы (MAP, FOLD, FILTER, ZIP и др.), лямбды и частичное применение макросов, разные контейнеры (sequence, tuple, list).

Правда её не так часто используют. Просто потому что она очень сложная (== высокий порог входа), без документации, а большинство задач решаются чем-то попроще.

Но как артефакт кайфово.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍132
#cpp

Day 48.

Если вам нужна сравнительно мощная, но более современная альтернатива chaos-pp, стоит взглянуть на Metalang99.

Это хорошая попытка сделать препроцессор похожим на функциональный язык программирования. Но что более важно, проект хорошо документирован. chaos-pp в этом плане проигрывает (и служит скорее исследовательским артефактом).

Так что Metalang99 — ваш второй выбор (после Boost.Preprocessor).

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
5👍4
#cpp

Day 49.

Одна из моделей, которую можно реализовать на макросах (хотя мы выяснили, что можно любую видимо) — continuation machine. Фактически это штука, в которой вы можете построить пайплайн работы с данными (но такими словами про неё не говорят, слишком современно). На картинке ниже страшная реализация для простого кейса работы с регистрами (разбирать мы ёе не будем, я хочу просто подышать.....).

Кстати репозиторий это что-то вроде оптимизированного интерпретатора Brainfuck. Есть и другие альтернативы: bfi, CPP_COMPLETE.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
11🤯3👍1
#cpp

Day 50.

Макросы можно класть на и доставать из (со) стека:

#define PRAMGA(...) _Pragma(#__VA_ARGS__)
#define POP(m) PRAMGA(pop_macro(#m))

#define X 1 POP(X)
#pragma push_macro("X")
#define X 2 POP(X)
#pragma push_macro("X")
#define X 3 POP(X)
#pragma push_macro("X")
#define X 4 POP(X)
X // 4
X // 3
X // 2
X // 1


Не уверен, что понимаю, зачем это нужно.

@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍3🤪2🔥1
#cpp

Day 51.

Есть ещё несколько интересных репозиториев, связанных с препроцессором:
datatype99 (фактически синтаксический сахар для генерации типов, но с дополнительными плюшками вроде паттерн матчинга)
interface99 (Golang/Rust inspired интерфейсы для C99)
boline (integer arithmetics with base 16).

У последнего код с вайбом ASCII-art.

И есть ещё подгон от подписчика про расширение в clang, позволяющее делать макросы рекурсивными. Названо оно __THIS_MACRO__:

#define fold_left(op, head, ...)(__VA_OPT__(__THIS_MACRO__(op,__VA_ARGS__)op)head)

fold_left(+, 1, 2, 3, 4) // раскрывается в ((((4)+3)+2)+1)


@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍7👏1