Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Appendices

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 29. Boost.Proto

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext
Boost 1.51

Упаковочные выражения

В Boost 1.51 Proto получил простые распаковочные узоры. При работе с преобразованиями Proto, распаковка выражений полезна для распаковки детей выражения в вызов функции или конструктор объекта, при этом необязательно применяя некоторые преобразования к каждому ребенку в свою очередь.

См. разделUnpacking Expressionsдля получения дополнительной информации.

Boost 1.44

Изменение поведения: proto:::<>

В Boost 1.44 поведение<proto::and_<>>как трансформация изменилось. Ранее в ней применялось только преобразование, связанное с последней грамматикой в наборе. Теперь он применяет все преобразования, но возвращает только результат последнего. Это заставляет его вести себя как оператор запятой C++. Например, грамматика, такая как:

proto::and_< G0, G1, G2 >

При оценке с выражением<e>теперь ведет себя так:

((void)G0()(e), (void)G1()(e), G2()(e))
[Note] Note

Почему отбрасывается пустота? Это чтобы избежать спор-зависимого поиска, который может найти перегруженного оператора запятой.

Изменение поведения: proto::as_expr() и proto::as_child()

Функции<proto::as_expr()>и<proto::as_child()>используются для гарантии того, что объект является выражением Прото, превращая его в одно, если он еще не является, используя необязательно указанный домен. В предыдущих выпусках, когда эти функции были переданы экспрессией Proto в домене, отличном от указанного, они применяли генератор указанного домена, что приводило к дважды завернутому выражению. Такое поведение удивило некоторых пользователей.

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

Изменение поведения: proto:: (pod_)generator<>и proto::basic_expr<>

Пользователи, знакомые с механизмом расширения Proto, вероятно, использовали<proto::generator<>>или<proto::pod_generator<>>шаблон обертки при определении своего домена. В прошлом Proto инстанцировал бы ваш шаблон обертки примерами<proto::expr<>>. В Boost 1.44 Proto теперь представляет шаблон обертки с экземплярами нового типа:<proto::basic_expr<>>.

Например:

// An expression wrapper
template<class Expr>
struct my_expr_wrapper;
// A domain
struct my_domain
  : proto::domain< proto::generator< my_expr_wrapper > >
{};
template<class Expr>
struct my_expr_wrapper
  : proto::extends<Expr, my_expr_wrapper<Expr>, my_domain>
{
  // Before 1.44, Expr was an instance of proto::expr<>
  // In 1.44, Expr is an instance of proto::basic_expr<>
};

Мотивацией для этого изменения было улучшение времени компиляции.<proto::expr<>>является дорогостоящим типом, поскольку он определяет множество функций-членов. При определении вашей собственной экспрессионной обертки экземпляр<proto::expr<>>является скрытой функцией элемента данных в вашей обертке, а члены<proto::expr<>>остаются неиспользованными. Следовательно, стоимость этих функций членов теряется. В противоположность этому<proto::basic_expr<>>является очень легким типом, не имеющим никаких функций.

Подавляющее большинство программ должны перекомпилироваться без каких-либо изменений источника. Однако, если где-то вы предполагаете, что вам будут даны конкретные экземпляры<proto::expr<>>, ваш код сломается.

Новая функция: Поддомены

В Boost 1.44 Proto представляет новую функцию под названием «поддомены». Это позволяет определить, что один домен совместим с другим таким образом, что выражения в одном домене могут свободно смешиваться с выражениями в другом. Вы можете определить один домен как поддомен другого, используя третий параметр шаблона<proto::domain<>>.

Например:

// Not shown: define some expression
// generators genA and genB
struct A
  : proto::domain< genA, proto::_ >
{};
// Define a domain B that is the sub-domain
// of domain A.
struct B
  : proto::domain< genB, proto::_, A >
{};

Выражения в доменах<A>и<B>могут иметь разные обертки (следовательно, разные интерфейсы), но они могут быть объединены в более крупные выражения. Без субдоменных отношений это было бы ошибкой. Домен результирующего выражения в этом случае будет<A>.

Полное описание поддоменов можно найти в справочных разделах<proto::domain<>>и<proto::deduce_domain>.

Новая функция: домен-специфический as_expr() и as_child()

Proto всегда позволял пользователям настраивать выражения пост-hoc, указывая генератор при определении своего домена. Но это никогда не позволяло пользователям контролировать, как Proto собирает суб-выражения. По состоянию на 1.44, пользователи теперь имеют эту мощность.

Пользователи, определяющие свой собственный домен, теперь могут указать, как<proto::as_expr()>и<proto::as_child()>работают в их домене. Они могут легко сделать это, определив вложенные шаблоны классов, называемые<as_expr>и / или<as_child>в своем классе домена.

Например:

struct my_domain
  : proto::domain< my_generator >
{
  typedef
      proto::domain< my_generator >
  base_domain;
  // For my_domain, as_child does the same as
  // what as_expr does by default.
  template<class T>
  struct as_child
    : base_domain::as_expr<T>
  {};
};

В приведенном выше примере<my_domain::as_child<>>просто откладывается до<proto::domain::as_expr<>>. Это приводит к тому, что все терминалы захватываются ценностью, а не ссылкой, а также хранят выражения ребенка по стоимости. Результатом является то, что выражения в<my_domain>безопасны для хранения в<auto>переменных, потому что они не будут иметь висячих ссылок на промежуточные временные выражения. (Естественно, это также означает, что конструкция выражения имеет дополнительные затраты времени выполнения на копирование, которые компилятор может или не может оптимизировать.)

Boost 1.43

В Boost 1.43 рекомендуемое использование<proto::extends<>>немного изменилось. Новое использование выглядит так:

// my_expr is an expression extension of the Expr parameter
template<typename Expr>
struct my_expr
  : proto::extends<Expr, my_expr<Expr>, my_domain>
{
    my_expr(Expr const &expr = Expr())
      : proto::extends<Expr, my_expr, my_domain>(expr)
    {}
    // NEW: use the following macro to bring
    // proto::extends::operator= into scope.
    BOOST_PROTO_EXTENDS_USING_ASSIGN(my_expr)
};

Новое дело - использование макроса<BOOST_PROTO_EXTENDS_USING_ASSIGN>() [58). Чтобы позволить операторам присваивания строить деревья выражений,<proto::extends<>>перегружает оператора присваивания. Однако для шаблона<my_expr>компилятор генерирует оператор присваивания копий по умолчанию, который скрывает их в<proto::extends<>>. Это часто нежелательно (хотя это зависит от синтаксиса, который вы хотите разрешить).

Ранее рекомендуемое использование было для этого:

// my_expr is an expression extension of the Expr parameter
template<typename Expr>
struct my_expr
  : proto::extends<Expr, my_expr<Expr>, my_domain>
{
    my_expr(Expr const &expr = Expr())
      : proto::extends<Expr, my_expr, my_domain>(expr)
    {}
    // OLD: don't do it like this anymore.
    using proto::extends<Expr, my_expr, my_domain>::operator=;
};

Хотя это работает в большинстве случаев, это все еще не подавляет неявную генерацию оператора назначения по умолчанию. В результате выражения формы<a= b>могут либо создавать шаблон выражения, либо выполнять задание на копирование в зависимости от того, являются ли типы<a>и<b>одинаковыми. Это может привести к тонким ошибкам, поэтому поведение изменилось.

<BOOST_PROTO_EXTENDS_USING_ASSIGN>вводит в сферу действия операторов уступки, определенных в<proto::extends<>>, а также подавляет генерацию оператора уступки копии.

Также обратите внимание, что шаблон класса<proto::literal<>>, который использует<proto::extends<>>, был вырезан для использования<BOOST_PROTO_EXTENDS_USING_ASSIGN>. Ниже приводится примерный код последствий:

proto::literal<int> a(1), b(2); // two non-const proto literals
proto::literal<int> const c(3); // a const proto literal
a = b; // No-op. Builds an expression tree and discards it.
       // Same behavior in 1.42 and 1.43.
a = c; // CHANGE! In 1.42, this performed copy assignment, causing
       // a's value to change to 3. In 1.43, the behavior is now
       // the same as above: build and discard an expression tree.

August 13, 2010

Ускорение 1.44: Прото получает поддомены и контроль над каждым доменом<proto::as_expr()>и<proto::as_child()>для удовлетворения потребностей Phoenix3.

August 11, 2008

Proto v4 сливается с багажником Boost с более мощным протоколом преобразования.

April 7, 2008

Прото принимается в буст.

March 1, 2008

Начинается обзор Proto's Boost.

January 11, 2008

Повышаю. Proto v3 приносит разделение грамматики и преобразований и «круглый» лямбда-синтаксис для определения преобразований на месте.

April 15, 2007

Повышаю. Xpressive портируется из компиляторов Proto в трансформаторы Proto. Поддержка старых компиляторов Proto отпадает.

April 4, 2007

Предварительное представление Proto Boost.

December 11, 2006

Идея преобразований, которые украшают правила грамматики, родилась в частной дискуссии по электронной почте с Джоэлем де Гусманом и Хартмутом Кайзером. Первые трансформаторы будут переданы CVS через 5 дней, 16 декабря.

November 1, 2006

Идея для<proto::matches<>>и всей грамматики вынашивается во время обсуждения с Хартмутом Кайзером в списке духов. Первая версия<proto::matches<>>проверяется в CVS через 3 дня. Сообщениездесь.

October 28, 2006

Прото возрождается, на этот раз с однородными типами выражения, которые являются POD. Объявлениездесь.

April 20, 2005

Прото рождается как крупная рефакторизация Boost. Метапрограммирование Xpressive. Proto предлагает типы выражений, операторские перегрузки и «компиляторы», раннюю формулировку того, что позже стало трансформациями. Объявлениездесь.

Типы экспрессии прото являются POD (Plain Old Data) и не имеют конструкторов. Они инициируются скобками следующим образом:

terminal<int>::type const _i = {1};

Причина в том, что объекты выражения, подобные<_i>выше, могут бытьстатически инициализированы. Почему важна статическая инициализация? Терминалы многих встраиваемых доменных языков, вероятно, будут глобальными объектами, такими как<_1>и<_2>из библиотеки Boost Lambda. Если бы эти объекты требовали инициализации во время выполнения, можно было бы использовать эти объекты до их инициализации. Это было бы плохо. Статически инициализированные объекты не могут быть использованы неправильно.

Любой, кто заглядывал в исходный код Proto, вероятно, задавался вопросом: "Почему весь грязный препроцессор порвался? Разве все это не могло быть реализовано поверх таких библиотек, как MPL и Fusion? Ответ заключается в том, что Proto мог быть реализован таким образом, и на самом деле был в какой-то момент. Проблема в том, что метапрограммирование шаблонов (TMP) обеспечивает более длительное время компиляции. В качестве основы, на которой будут построены другие библиотеки с TMP, Proto должен быть максимально легким. Это достигается путем предпочтения препроцессорного метапрограммирования шаблонному метапрограммированию. Расширение макроса гораздо эффективнее, чем создание шаблона. В некоторых случаях для компиляции «чистой» версии требуется в 10 раз больше времени, чем для «грязной».

«Чистая и медленная» версия Proto все еще можно найти по адресу http://svn.boost.org/svn/boost/branches/proto/v3. Любой, кто заинтересован, может загрузить его и убедиться, что он, на самом деле, необоснованно медленно компилируется. Обратите внимание, что разработка этой ветки была прекращена, и она не соответствует текущему интерфейсу Proto.

Много уже было написано о диспетчеризации на чертах типа с использованием методов SFINAE (Substitution Failure Is Not An Error) на C++. Есть библиотека Буст, Буст. Enable_if, чтобы сделать технику идиоматической. Прото-отправления по признакам типа широко, но он не очень часто используется<enable_if<>>. Скорее, это отправки, основанные на наличии или отсутствии вложенных типов, часто типизированных для пустоты.

Взять хотя бы<is_expr<>>. Можно было бы написать что-то вроде этого:

template<typename T>
struct is_expr
  : is_base_and_derived<proto::some_expr_base, T>
{};

Скорее, он реализуется следующим образом:

template<typename T, typename Void = void>
struct is_expr
  : mpl::false_
{};
template<typename T>
struct is_expr<T, typename T::proto_is_expr_>
  : mpl::true_
{};

Это зависит от того, что специализация будет предпочтительной, если<T>имеет вложенную<proto_is_expr_>, которая является типдефом для<void>. Все типы экспрессии Proto имеют такой вложенный типдеф.

Почему Proto делает это именно так? Причина в том, что, проведя обширные тесты, пытаясь улучшить время компиляции, я обнаружил, что этот подход компилируется быстрее. Требуется ровно один шаблон. Другой подход требует по крайней мере 2:<is_expr<>>и<is_base_and_derived<>>, плюс любые шаблоны<is_base_and_derived<>>.

В некоторых местах Прото должен знать, можно ли вызвать объект функции<Fun>с определенными параметрами и предпринять резервное действие, если нет. Это происходит в<proto::callable_context<>>и в<proto::call<>>преобразовании. Откуда Прото знает? Это включает в себя сложное метапрограммирование. Вот как.

Другой способ постановки вопроса заключается в попытке реализовать следующую булеву метафункцию<can_be_called<>>, которая проверяет, можно ли вызвать объект функции<Fun>с параметрами типа<A>и<B>:

template<typename Fun, typename A, typename B>
struct can_be_called;

Во-первых, мы определяем следующую структуру<dont_care>, которая имеет неявное преобразование из чего-либо. И не просто любое неявное преобразование; оно имеет преобразование эллипса, которое является наихудшим возможным преобразованием для целей разрешения перегрузки:

struct dont_care
{
    dont_care(...);
};

Нам также нужен некоторый частный тип, известный только нам с перегруженным оператором запятой (!), и некоторые функции, которые обнаруживают наличие этого типа и типов возврата с различными размерами, следующие:

struct private_type
{
    private_type const &operator,(int) const;
};
typedef char yes_type;      // sizeof(yes_type) == 1
typedef char (&no_type)[2]; // sizeof(no_type)  == 2
template<typename T>
no_type is_private_type(T const &);
yes_type is_private_type(private_type const &);

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

template<typename Fun>
struct funwrap2 : Fun
{
    funwrap2();
    typedef private_type const &(*pointer_to_function)(dont_care, dont_care);
    operator pointer_to_function() const;
};

Со всеми этими битами и частями мы можем реализовать<can_be_called<>>следующим образом:

template<typename Fun, typename A, typename B>
struct can_be_called
{
    static funwrap2<Fun> &fun;
    static A &a;
    static B &b;
    static bool const value = (
        sizeof(no_type) == sizeof(is_private_type( (fun(a,b), 0) ))
    );
    typedef mpl::bool_<value> type;
};

Идея состоит в том, чтобы сделать так, чтобы<fun(a,b)>всегда компилировалась, добавляя нашу собственную перегрузку двоичной функции, но делая это таким образом, чтобы мы могли определить, была ли выбрана наша перегрузка или нет. И мы подтасовываем его так, чтобы наша перегрузка была выбрана, если действительно нет лучшего варианта. Вот как это работает<can_be_called<>>.

Мы обертываем<Fun>в тип, который имеет неявное преобразование в указатель на двоичную функцию. Объект<fun>типа класса можно вызвать как<fun(a,b)>, если он имеет такого оператора преобразования, но поскольку он включает в себя оператора преобразования, определенного пользователем, он менее предпочтителен, чем перегруженный<operator()>, который не требует такого преобразования.

Указатель функции может принимать любые два аргумента в силу типа<dont_care>. Последовательность преобразования для каждого аргумента гарантированно является наихудшей возможной последовательностью преобразования: неявное преобразование через эллипсис и определяемое пользователем преобразование в<dont_care>. В целом это означает, что<funwrap2<Fun>()(a,b)>всегда будет компилировать, но он выберет нашу перегрузку только в том случае, если действительно нет лучшего варианта.

Если есть лучший вариант - например, если<Fun>имеет перегруженную функцию оператора вызова, такую как<void operator()(Aa,Bb)>- тогда<fun(a,b)>решит вместо этого. Теперь вопрос заключается в том, как определить, какая функция была выбрана при разрешении перегрузки.

Обратите внимание, как<fun(a,b)>появляется в<can_be_called<>>:<(fun(a,b),0)>. Почему мы используем оператор запятой? Причина в том, что мы используем это выражение в качестве аргумента для функции. Если тип возврата<fun(a,b)>является<void>, он не может юридически использоваться в качестве аргумента для функции. Оператор запятой обходит проблему.

Это также должно объяснить цель оператора перегруженной запятой в<private_type>. Тип возврата указателя к функции<private_type>. Если разрешение перегрузки выбирает нашу перегрузку, то тип<(fun(a, b), 0)><private_type>. В противном случае<int>. Этот факт используется для отправки на любую перегрузку<is_private_type()>, которая кодирует его ответ в размере его типа возврата.

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

Я хотел бы поблагодарить Джоэла де Гусмана и Хартмута Кайзера за то, что они были готовы использовать Proto для своей работы над Spirit-2 и Karma, когда Proto был не более чем видением. Их требования и отзывы были незаменимы.

Спасибо Томасу Хеллеру и Хартмуту за их отзывы и предложения во время редизайна Феникса. Это усилие дало несколько ценных расширенных функций, таких как поддомены, внешние преобразования и настройка на домен<as_child>.

Спасибо Даниэлю Джеймсу за предоставление патча для устранения зависимости от устаревших макросов конфигурации для функций C++0x.

Спасибо Джоэлу Фальку и Кристофу Генри за их энтузиазм, поддержку, обратную связь и юмор; и за добровольчество, чтобы быть со-поддержкой Прото.

Спасибо Дэйву Абрахамсу за особенно подробный обзор и за то, что он сделал VM с msvc-7.1 доступным, чтобы я мог отслеживать проблемы переносимости на этом компиляторе.

Большое спасибо Даниэлю Уоллину, который впервые реализовал код, используемый для поиска общего домена среди множества, учитывающего супер- и поддомены. Спасибо также Джереми Уиллкоку, Джону Байтвею и Кришне Ачутану, которые предложили альтернативные решения этой сложной проблемы программирования.

Спасибо также разработчикамPETE. Я нашел там много хороших идей.


PrevUpHomeNext

Статья Appendices раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 29. Boost.Proto может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Chapter 29. Boost.Proto ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:02:42/0.013372898101807/0