Рубрика «метапрограммирование» - 3

Практика метапрограммирования на C++: бинарное дерево поиска на этапе компиляции - 1Создатели шаблонов в C++ заложили основу целого направления для исследований и разработки: оказалось, что язык шаблонов C++ обладает полнотой по Тьюрингу, то есть метапрограммы (программы, предназначенные для работы на этапе компиляции) C++ в состоянии вычислить всё вычислимое. На практике мощь шаблонов чаще всего применяется при описании обобщенных структур данных и алгоритмов: STL (Standard Template Library) тому живой пример.

Однако, с приходом C++11 с его variadic-шаблонами, библиотекой type_traits, кортежами и constexpr'ами метапрограммирование стало более удобным и наглядным, открыв дорогу к реализации многих идей, которые раньше можно было воплотить только с помощью расширений конкретного компилятора или сложных многоэтажных макросов.

В данной статье будет разработана реализация бинарного дерева поиска времени компиляции: структуры данных, являющейся логическим «расширением» кортежа. Мы воплотим бинарное дерево в виде шаблона и попрактикуемся в метапрограммировании: переносе рекурсивных и не только алгоритмов на язык шаблонов C++.
Читать полностью »

JSON-сериализатор на быстрых шаблонах - 1

В чем проблема текстовых форматов обмена данными? Они медленные. И не просто медленные, а чудовищно медленные. Да, они избыточны, по сравнению с бинарными протоколами и, по идее, текстовый сериализатор должен быть медленнее примерно на столько же, на сколько он избыточен. Но на практике получается, что текстовые сериализаторы иной раз на порядки уступают бинарным аналогам.

Я не буду рассуждать о преимуществах JSON перед бинарными форматами — у каждого формата есть своя область применения, в которой он хорош. Но зачастую мы вынуждены отказываться от чего-то удобного в пользу не очень комфортного в силу катастрофической неэффективности первого. Разработчики отказываются от JSON, даже если он прекрасно подходит для решения задачи, только из-за того, что он оказывается узким местом в системе. Конечно же, виноват не JSON сам по себе, а реализации соответствующих библиотек.

В этой статье я расскажу не только о проблемах парсеров текстовых форматов вообще и JSON в частности, но и о нашей библиотеке, которую мы используем уже много лет в самых высоконагруженных проектах. Она настолько нас устраивает и в плане быстродействия, и в плане удобства использования, что порой отказываемся от бинарного формата там, где бы он больше подошел. Конечно же, я имею в виду некие пограничные условия, без претензий на все случаи жизни.

Читать полностью »

I. Предыстория

Я много лет использую UltraEdit как редактор на самые разные случаи жизни. Одна из основных причин — быстрая работа с гигабайтными файлами без загрузки их в память. Для программирования на JavaScript он тоже достаточно удобен, вот только с одним существенным недостатком: автодополнение в нём основывается на достаточно бедном, жёстко заданном списке ключевых слов и глобальных переменных, вдобавок отстающем от развития языка. Как-то я задался вопросом, можно ли пополнить этот список полным перечнем всех готовых свойств и методов, какие только можно ввести в контексте Node.js и Web API (браузера). Где бы такой список можно раздобыть? Мне приходили в голову такие варианты:

  1. Готовый перечень, кем-то составляемый и обновляемый для всеобщего пользования, вроде библиотеки globals, но полнее.

  2. Парсинг документации (спецификация ECMAScript, сайты MDN и Node.js и т.п.), вручную или программно.

  3. Получение списка метапрограммированием.

Читать полностью »

Для многих программистов, которые используют или желали бы использовать Go на практике, отсутсвие механизмов параметрического полиморфизма в языке является большой печалью. Но не все так плохо как может показаться на первый взгляд.

Конечно в Go нельзя писать обобщенные программы, например в стиле C++ templates, которые бы практически не влияли на затраты процессорного времени. Такого механизма в языке нет и, вполне возможно, что не предвидится.

С другой стороны, язык представляет довольно мощный встроенный пакет reflect, которой позволяет производить рефлексию как объектов, так и функций. Если не ставить быстродействие во главу угла, то с помощью этого пакета можно достигать интересных и гибких решений.

В этой статье я покажу как реализовать for each в виде типонезависимой рефлексивной функции.

Читать полностью »

Каждый интересующийся шаблонами в С++ скорее всего слышал об их Тьюринг-полноте и связанных с этим шутках про «we put a language in your language, so you can program while you program». В этом посте я расскажу как с помощью шаблонов и константных выражений построить настоящую машину Тьюринга, вычисляющую результат своей работы во время компиляции, на которой можно будет запускать уже существующие программы. Например усердный бобер с 4 состояниями и 2 символами выглядит как-то так:

ADD_STATE(A);
ADD_STATE(B);
ADD_STATE(C);
ADD_STATE(D);

ADD_RULE(A, Blank, 1, Right, B);
ADD_RULE(A, 1, 1, Left, B);

ADD_RULE(B, Blank, 1, Left, A);
ADD_RULE(B, 1, Blank, Left, C);

ADD_RULE(C, Blank, 1, Right, Stop);
ADD_RULE(C, 1, 1, Left, D);

ADD_RULE(D, Blank, 1, Right, D);
ADD_RULE(D, 1, Blank, Right, A);

using tape = Tape<Blank>;
using machine = Machine<A, 0, tape>;
using result = Run<machine>::type;

int main() {
    print(result());
    return 0;
}

На выходе, как и положено, получаем

1 _ 1 1 1 1 1 1 1 1 1 1 1 1 

Тут можно посмотреть на код: https://ideone.com/MvBU3Z. Желающие узнать как все устроено внутри, добро пожаловать под кат.
Читать полностью »

Возник на днях у нас вопрос: «Как спрятать от любителей hex-редаторов строчки текста в скомпилированном приложении?». Но спрятать так, чтобы это не требовало особых усилий, так, между прочим…
Задача состоит в том, что бы использовать в коде строки как обычно, но при этом в исполняемом файле эти строки в явном виде не хранились, возможности сторонних утилит, которые работают с уже скомпилированными бинарными файлами, задействовать так же не хочется, все нужно делать из обычного C++ кода.
Читать полностью »

Pipe matching в ЯП Clojure (метапрограммирование в Lisp для начинающих) - 1

Введение

Несколько дней назад я открыл для себя замечательный ЯП Clojure — один из современных диалектов Lisp, особенностью которого является хорошая реализация средств многопоточности, компиляция в байткод jvm, соответственно возможность использования java — библиотек, jit-компиляция и т.д. Про Clojure можно почитать например тут. Но в этой статье речь пойдёт о метапрограммировании. Lisp устроен таким образом, что данные и код в нём — одно и то же. Объявления функций, макросов, вызовы функций, развёртывание макросов — в Lisp это всё просто списки, возможно вложенные друг в друга.

(defn square [foo] (* foo foo))
(defmacro show-it [foo] `(println ~foo))

Такое единство кода и данных предоставляет мощные возможности для метапрограммирования — код который пишет код, который пишет код, который пишет код и т.д. — это самое обычное дело для программирования на Lisp. В compile-time нам полностью доступен весь функционал языка, мы можем вызывать функции, развёртывать макросы, возможно рекурсивно. Например, если мы определим вот такой макрос:
Читать полностью »

Вдохновившись публикацией «Logger с функциями-членами, которых нет», решил выдать на всеобщее обозрение свой мета-велосипед для рефлекшена, которым вполне успешно пользуюсь, и не только для логгирования. Но начнем всё же с простого логгирования, продолжая вышеупомянутую публикацию.

При реализации логгирования, задачи для себя были поставлены следующие:

  • Решить задачу на «мета-уровне», чтобы быть отвязанным от конечной реализации
  • Фронтенд апи для логгирования должен быть простым и прозрачным
  • Иметь возможность выключать ненужные уровни логгирования на этапе компиляции одной константой; например: все что выше LOG_NOTICE не должно попасть в результирующий бинари

Фронтенд выглядит так:

1. В конструкторе CConnection мы логгируем:

TestLog::Log<LOG_NOTICE>() << *this << "connection created";

2. Где CConnection унаследован от

public TLogHeader<'c', CConnection>

3.Который, используя CRTP, знает, что в CConnection есть такое:

	using this_t = CConnection;
	int m_id;           using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>;
	std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>;
    char m_state;       using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>;
	using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>;

3. И превращает эти данные в строчку лога:

c:23:test_conn 1:a:connection created

Где через двоеточие перечислены: с — первый параметр шаблона TLogHeader, и далее значения m_id, m_name, m_state

В моем случае все это дальше пишется в rsyslog и там разбирается в соответствующие поля для MongoDB (в зависимости от типа заголовка, т.е. первый параметр до двоеточия).

Сейчас этот подход у меня в продакшене на высоконагруженной системе, так что любые конструктивные камни будут в пику, или лучше под кат. А если лень, то вот работающий пример на http://coliru.stacked-crooked.com/ т.к. по катом кода на 250 строк, но очень много букв в описании.
Читать полностью »

Язык Julia не поддерживает такую технику программирования, хорошо зарекомендовавшую себя в языках Haskell, Prolog, Erlang, Scala, Mathematica, как pattern matching. Но разрешает писать макросы, которые позволяют исправить этот фатальный недостаток. Выглядит это примерно так:

julia> immutable X a end

julia> immutable Y a ; b end

julia> @case(Y(X(9),2),  Y(4,3)-> 55, Y(X(k),2)->1+k)
10

Исходный код доступен на github.
Похожую (но гораздо более развитую и готовую для использования) можно взять здесь, но она слишком большая, что бы разбирать ее как пример в статье.
Читать полностью »

В первой части мы обсудили, как добиться переноса определения шаблона фунции из заголовочного файла в исходник, если набор типов, для которых должен быть инстанциирован шаблон известен заранее. В этой части мы посмотрим, как добиться этого красиво.
Читать полностью »


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js