#cpp
Day 40.
Представьте, что вы хотите на препроцессоре писать не макросами, а на каком-нибудь Lisp-like языке программирования (ну просто попытайтесь представить, что вы этого хотите).
Есть такая библиотека
или
Язык на самом деле мощнее. Можно объявлять переменные, писать функции, рекурсию, списками пользоваться, ленивые вычисления мутить (вроде как).
Можем вот факториал вычислить (это всё будет вычислено препроцессором!):
(возможно оно не скомпилируется, но суть такая)
Как это работает? И что это за 8 перед командами?
Я не буду рассказывать про устройство библиотеки, но расскажу основной механизм.
Во время лексического анализа препроцессор не знает ни про какие числа. Он знает про токены языка. А они бывают разные:
Нас интересует pp-number. Ниже примеры того, что является pp-number:
И пусть вторую пачку компилятор не сможет разобрать как литералы, это всё равно pp-number (данные, а не identifier). А макросы ищутся только среди идентификаторов.
Поэтому библиотека пользуется такими pp-number сколько нужно и не боится, что препроцессор раскроет конструкции раньше времени (8 для префикса автор выбрал по личной симпатии видимо).
После этого перехода в мир стандартных макросов уже начинается жоское вычисление всего вокруг.
Чего только не напридумывают.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
Кроме хорошо знакомого вам
Подразумевалось, что
после чего вы могли писать
То есть с помощью
Ещё утверждения можно было отменять с помощью
И больше это неправда.
Фактически это примитивная логическая база знаний.
Выше я писал «был», потому что в C11 #assert и #unassert не стало. Потому что фактически почти никто не реализовал. Все писали примерно:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
Представьте такую ситуацию: Вы мегаумный программист, разрабатывающий инфру в какой-нибудь компании. И Вы хорошо понимаете, какие ошибки совершают ваши коллеги чаще других. Вы бы хотели оградить коллег от частых ошибок. Например, запретить пользоваться какой-то функцией из стандартной библиотеки и сообщить в ошибке альтернативу.
Или может вы пишете какой-то тулинг для сбора статистики и хотите подменить стандартную функцию своей, которая будет сначала собирать какую-то информацию, а потом как обычно вызывать стандартное решение.
Для решения таких задач можно подменить стандартный хедер на свой.
Для начала его надо реализовать:
И начать компилировать вашу программу как-нибудь так:
(тут мы указываем путь к хедерам, так как наш
Или через cmake как-то так:
Теперь пользователь будет писать
и вместо системного хедера подключится ваш. Круто?
А видите ли вы какую-нибудь проблему в реализации my_headers/string.h?
Например, что внутри мы тоже подключаем
Какая-то рекурсия!
Для решения этой проблемы некоторые компиляторы реализовали нестандартное расширение
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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🔥5❤3🤯2🌚1
#cpp
Day 43.
Так как писать код на препроцессоре бывает неимоверно неприятно, есть тулы в помощь. Например, ppstep, который позволяет интерактивно (пошагово) разбираться в раскрытии ваших макросов и всего вокруг.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
Day 44.
Сегодня ссылка, где Paul Mensodines скрупулёзно поясняет механизм раскрытия макросов: How macro expansion works.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍9
#cpp
Day 45.
Мы уже обсуждали, как сделать
Sequence это просто набор элементов в скобках:
Тут 5 элементов. Но конечно мы не ограничиваемся числами:
Основные операции иногда реализуются просто:
В конце на самом деле надо ещё как-то убрать
Другие операции могут быть чуть сложнее, но концептуально там ничего такого. В любом случае самим реализовывать вам не нужно. Берите
Там ещё и итерироваться можно, но про это завтра.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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, но иногда лучше вовремя остановиться и заиспользовать что-то готовое:
Получим
Как это реализовано?
Ну вот примерно как вчера откусываете по одному токену из начала и применяете к нему «функтор». Чего непонятно-то???
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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 — настоящая глыба в мире препроцессорных библиотек. Там вообще всё что хотите. Рекурсия, высокоуровневые алгоритмы (
Правда её не так часто используют. Просто потому что она очень сложная (== высокий порог входа), без документации, а большинство задач решаются чем-то попроще.
Но как артефакт кайфово.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 47.
Я уже упоминал Paul Mensodines, который написал огромное письмо про механизм раскрытия макросов.
И упоминал про Boost.Preprocessor.
Вот Paul когда-то написал chaos-pp — настоящая глыба в мире препроцессорных библиотек. Там вообще всё что хотите. Рекурсия, высокоуровневые алгоритмы (
MAP, FOLD, FILTER, ZIP и др.), лямбды и частичное применение макросов, разные контейнеры (sequence, tuple, list). Правда её не так часто используют. Просто потому что она очень сложная (== высокий порог входа), без документации, а большинство задач решаются чем-то попроще.
Но как артефакт кайфово.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍12❤2
#cpp
Day 48.
Если вам нужна сравнительно мощная, но более современная альтернатива chaos-pp, стоит взглянуть на Metalang99.
Это хорошая попытка сделать препроцессор похожим на функциональный язык программирования. Но что более важно, проект хорошо документирован. chaos-pp в этом плане проигрывает (и служит скорее исследовательским артефактом).
Так что Metalang99 — ваш второй выбор (после Boost.Preprocessor).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 48.
Если вам нужна сравнительно мощная, но более современная альтернатива chaos-pp, стоит взглянуть на Metalang99.
Это хорошая попытка сделать препроцессор похожим на функциональный язык программирования. Но что более важно, проект хорошо документирован. chaos-pp в этом плане проигрывает (и служит скорее исследовательским артефактом).
Так что Metalang99 — ваш второй выбор (после Boost.Preprocessor).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
❤4👍4
#cpp
Day 49.
Одна из моделей, которую можно реализовать на макросах (хотя мы выяснили, что можно любую видимо) — continuation machine. Фактически это штука, в которой вы можете построить пайплайн работы с данными (но такими словами про неё не говорят, слишком современно). На картинке ниже страшная реализация для простого кейса работы с регистрами (разбирать мы ёе не будем, я хочу просто подышать.....).
Кстати репозиторий это что-то вроде оптимизированного интерпретатора Brainfuck. Есть и другие альтернативы: bfi, CPP_COMPLETE.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 49.
Одна из моделей, которую можно реализовать на макросах (хотя мы выяснили, что можно любую видимо) — continuation machine. Фактически это штука, в которой вы можете построить пайплайн работы с данными (но такими словами про неё не говорят, слишком современно). На картинке ниже страшная реализация для простого кейса работы с регистрами (разбирать мы ёе не будем, я хочу просто подышать.....).
Кстати репозиторий это что-то вроде оптимизированного интерпретатора Brainfuck. Есть и другие альтернативы: bfi, CPP_COMPLETE.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
❤10🤯3👍1
#cpp
Day 50.
Макросы можно класть на и доставать из (со) стека:
Не уверен, что понимаю, зачем это нужно.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
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.
👍2🤪1