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

Users' Guide

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

Compilers, Compiler Construction Toolkits, and Proto

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

Библиотека, построенная с помощью Proto, по сути, является компилятором для встроенного доменного языка (EDSL). Он также имеет передний конец, промежуточную форму и задний конец. Передняя часть состоит из символов (например, терминалов), членов, операторов и функций, которые составляют видимые пользователям аспекты EDSL. Задний конец состоит из контекстов оценки и преобразований, которые придают смысл и поведение шаблонам выражения, генерируемым передним концом. Между ними находится промежуточная форма: сам шаблон выражения, который является абстрактным деревом синтаксиса в очень реальном смысле.

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

Руководство для пользователей организовано следующим образом. После руководстваGetting Startedмы рассмотрим инструменты, которые Proto предоставляет для определения и управления тремя основными частями компилятора:

Front Ends

Как определить аспекты вашего EDSL, с которыми ваши пользователи будут взаимодействовать напрямую.

Intermediate Form

Как выглядят шаблоны выражения Proto, как обнаружить их структуру и получить доступ к их составляющим.

Back Ends

Как определить контексты оценки и преобразования, которые заставляют шаблоны выражения делать интересные вещи.

После этого вам может быть интересно посмотреть некоторыеПримеры, чтобы получить лучшее представление о том, как все части сочетаются друг с другом.

Getting Proto

Вы можете получить Proto, загрузив Boost (Proto в версии 1.37 и более поздней), или зайдя в репозиторий SVN Boost на SourceForge.net.http://svn.boost.org/trac/boost/wiki/BoostSubversionи следовать инструкциям для анонимного доступа к SVN.

Building with Proto

Proto - это библиотека шаблонов только для заголовков, что означает, что вам не нужно изменять скрипты сборки или ссылаться на какой-либо отдельный файл lib, чтобы использовать его. Все, что вам нужно сделать, это<#include <boost/proto/proto.hpp>>. Или вы можете просто включить ядро Proto<#include <boost/proto/core.hpp>>и какие контексты и преобразования вы используете.

Requirements

Прото зависит от Boost. Вы должны использовать либо версию Boost 1.34.1 или выше, либо версию в багажнике SVN.

Supported Compilers

В настоящее время, Boost. Известно, что Proto работает над следующими компиляторами:

  • Visual C++ 8 и выше
  • GNU C++ 3.4 и выше
  • Intel для Linux 8.1 и выше
  • Intel для Windows 9.1 и выше
[Note]Note

Пожалуйста, отправьте любые вопросы, комментарии и отчеты об ошибках в ericboostprocom.

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

Functions

Все функции Прото определены в пространстве имен<boost::proto>. Например, существует функция под названием<value()>, определенная в<boost::proto>, которая принимает терминальное выражение и возвращает значение терминала.

Metafunctions

Прото определяетметафункции, которые соответствуют каждой из свободных функций Прото. Метафункции используются для вычисления типов возврата функций. Все метафункции Прото живут в пространстве имен<boost::proto::result_of>и имеют то же название, что и функции, которым они соответствуют. Например, существует шаблон класса<boost::proto::result_of::value<>>, который можно использовать для вычисления типа возврата функции<boost::proto::value()>.

Function Objects

Прото определяетобъект функцииэквиваленты всех его свободных функций. (Объект функции — это экземпляр типа класса, который определяет функцию члена<operator()>.) Все типы объектов функции Прото определены в пространстве имен<boost::proto::functional>и имеют то же название, что и их соответствующие свободные функции. Например,<boost::proto::functional::value>— класс, который определяет объект функции, который делает то же самое, что<boost::proto::value()>свободная функция.

Primitive Transforms

Прото также определяетпримитивные преобразования— типы классов, которые могут использоваться для составления более крупных преобразований для манипулирования деревьями экспрессии. Многие свободные функции Прото имеют соответствующие примитивные преобразования. Они живут в пространстве имен<boost::proto>, и их имена имеют важное значение. Например, преобразование, соответствующее функции<value()>, называется<boost::proto::_value>.

В нижеследующей таблице резюмируется вышеупомянутое обсуждение:

Table 29.1. Proto Naming Conventions

сущность

Пример

Свободная функция

<boost::proto::value()>

Метафункция

<boost::proto::result_of::value<>>

Функциональный объект

<boost::proto::functional::value>

Трансформация

<boost::proto::_value>


Ниже приведена очень простая программа, которая использует Proto для создания шаблона выражения, а затем выполняет его.

#include <iostream>
#include <boost/proto/proto.hpp>
#include <boost/typeof/std/ostream.hpp>
using namespace boost;
proto::terminal< std::ostream & >::type cout_ = { std::cout };
template< typename Expr >
void evaluate( Expr const & expr )
{
    proto::default_context ctx;
    proto::eval(expr, ctx);
}
int main()
{
    evaluate( cout_ << "hello" << ',' << " world" );
    return 0;
}

Эта программа предусматривает следующее:

hello, world

Эта программа создает объект, представляющий операцию вывода, и передает его функции<evaluate()>, которая затем выполняет его.

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

cout_ << "hello" << ',' << " world"

Протофидное подвыражение<cout_>, которое является протоификацией<std::cout>. Наличие<cout_>«заражает» выражение и учитывает перегрузки оператора строительства деревьев Прото. Любые буквы в выражении затем Protofied, обернув их в терминал Proto, прежде чем они будут объединены в более крупные выражения Proto.

После того, как операторские перегрузки Proto построили дерево экспрессии, выражение может быть лениво оценено позже. Это то, что<proto::eval()>. Это общий оценщик выражений ходьбы по дереву, поведение которого настраивается черезконтекстпараметр. Использование<proto::default_context>присваивает операторам стандартные значения в выражении. (Используя другой контекст, вы можете дать операторам в своих выражениях различную семантику. По умолчанию Proto не делает никаких предположений о том, что на самом деле означают операторы.

Proto Design Philosophy

Прежде чем продолжить, давайте используем приведенный выше пример, чтобы проиллюстрировать важный принцип проектирования Proto. Шаблон выражения, созданный в примереHello World, является полностью общим и абстрактным. Он никоим образом не привязан к какой-либо конкретной области или приложению и не имеет какого-либо конкретного значения или поведения самостоятельно, пока не будет оценен вконтексте. Шаблоны выражения на самом деле просто неоднородные деревья, что может означать что-то в одной области, а что-то совершенно другое в другой.

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

"Здравствуй, мир" - это хорошо, но это не очень далеко. Давайте использовать Proto для создания EDSL (встроенного доменного языка) для калькулятора с ленивой оценкой. Мы посмотрим, как определить терминалы в мини-языке, как составить их в более крупные выражения и как определить контекст оценки, чтобы ваши выражения могли выполнять полезную работу. Когда мы закончим, у нас будет мини-язык, который позволит нам объявить лениво оцененное арифметическое выражение, такое как<(_2 -_1)/_2 *100>, где<_1>и<_2>являются держателями для значений, которые будут переданы при оценке выражения.

Defining Terminals

Первый из них – это<_1>и<_2>. Для этого мы используем метафункцию<proto::terminal<>>.

// Define a placeholder type
template<int I>
struct placeholder
{};
// Define the Protofied placeholder terminals
proto::terminal<placeholder<0> >::type const _1 = {{}};
proto::terminal<placeholder<1> >::type const _2 = {{}};

Поначалу инициализация может показаться немного странной, но есть веская причина для этого. Объекты<_1>и<_2>выше не требуют построения времени выполнения — онистатически инициализированы, что означает, что они по существу инициализированы во время компиляции. См. разделСтатическая инициализацияв приложенииОбоснованиедля получения дополнительной информации.

Constructing Expression Trees

Теперь, когда у нас есть терминалы, мы можем использовать перегрузки оператора Proto, чтобы объединить эти терминалы в более крупные выражения. Например, мы можем сразу сказать такие вещи, как:

// This builds an expression template
(_2 - _1) / _2 * 100;

Это создает дерево экспрессии с узлом для каждого оператора. Тип полученного объекта большой и сложный, но мы не очень заинтересованы в нем сейчас.

До сих пор объект — это просто дерево, представляющее выражение. У него нет поведения. В частности, это еще не калькулятор. Ниже мы рассмотрим, как сделать его калькулятором, определив контекст оценки.

Evaluating Expression Trees

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

struct calculator_context
  : proto::callable_context< calculator_context const >
{
    // Values to replace the placeholders
    std::vector<double> args;
    // Define the result type of the calculator.
    // (This makes the calculator_context "callable".)
    typedef double result_type;
    // Handle the placeholders:
    template<int I>
    double operator()(proto::tag::terminal, placeholder<I>) const
    {
        return this->args[I];
    }
};

В<calculator_context>мы укажем, как Proto должен оценивать терминалы заполнителя, определяя соответствующие перегрузки оператора вызова функции. Для любых других узлов в дереве экспрессии (например, арифметических операций или неплацентарных терминалов) Proto будет оценивать экспрессию «по умолчанию». Например, двоичный узел плюс оценивается путем первой оценки левого и правого операндов и добавления результатов. Оценка по умолчанию Proto использует библиотекуBoost.Typeofдля вычисления типов возврата.

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

calculator_context ctx;
ctx.args.push_back(45); // the value of _1 is 45
ctx.args.push_back(50); // the value of _2 is 50
// Create an arithmetic expression and immediately evaluate it
double d = proto::eval( (_2 - _1) / _2 * 100, ctx );
// This prints "10"
std::cout << d << std::endl;

Позже мы увидим, как определить более интересные контексты оценки и преобразования выражения, которые дают вам полный контроль над тем, как оцениваются ваши выражения.

Customizing Expression Trees

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

// Forward-declare an expression wrapper
template<typename Expr>
struct calculator;
// Define a calculator domain. Expression within
// the calculator domain will be wrapped in the
// calculator<> expression wrapper.
struct calculator_domain
  : proto::domain< proto::generator<calculator> >
{};

Тип<calculator<>>будет экспрессионной оберткой. Он будет вести себя так же, как выражение, которое он обертывает, но у него будут дополнительные функции, которые мы определим.<calculator_domain>- это то, что сообщает Прото о нашей обертке. Приведены ниже в определении<calculator<>>. Читайте дальше для описания.

// Define a calculator expression wrapper. It behaves just like
// the expression it wraps, but with an extra operator() member
// function that evaluates the expression.    
template<typename Expr>
struct calculator
  : proto::extends<Expr, calculator<Expr>, calculator_domain>
{
    typedef
        proto::extends<Expr, calculator<Expr>, calculator_domain>
    base_type;
    calculator(Expr const &expr = Expr())
      : base_type(expr)
    {}
    typedef double result_type;
    // Overload operator() to invoke proto::eval() with
    // our calculator_context.
    double operator()(double a1 = 0, double a2 = 0) const
    {
        calculator_context ctx;
        ctx.args.push_back(a1);
        ctx.args.push_back(a2);
        return proto::eval(*this, ctx);
    }
};

Структура<calculator<>>является выражениемрасширения. Он использует<proto::extends<>>для эффективного добавления дополнительных членов к типу выражения. При составлении более крупных выражений из более мелких Proto отмечает, в какой области находятся меньшие выражения. Большее выражение находится в том же домене и автоматически обернуто в обертку расширения домена.

Все, что еще предстоит сделать, это поместить наших держателей в область калькулятора. Мы делаем это, завернув их в нашу обертку<calculator<>>, как показано ниже:

// Define the Protofied placeholder terminals, in the
// calculator domain.
calculator<proto::terminal<placeholder<0> >::type> const _1;
calculator<proto::terminal<placeholder<1> >::type> const _2;

Любое более крупное выражение, содержащее эти заполнители, будет автоматически обернуто в обертку<calculator<>>и иметь нашу<operator()>перегрузку. Это означает, что мы можем использовать их в качестве функциональных объектов следующим образом.

double result = ((_2 - _1) / _2 * 100)(45.0, 50.0);
assert(result == (50.0 - 45.0) / 50.0 * 100));

Поскольку выражения калькулятора теперь являются действительными функциональными объектами, мы можем использовать их со стандартными алгоритмами, как показано ниже:

double a1[4] = { 56, 84, 37, 69 };
double a2[4] = { 65, 120, 60, 70 };
double a3[4] = { 0 };
// Use std::transform() and a calculator expression
// to calculate percentages given two input sequences:
std::transform(a1, a1+4, a2, a3, (_2 - _1) / _2 * 100);

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

Detecting Invalid Expressions

Возможно, вы заметили, что вам не нужно определять перегруженный<operator-()>или<operator/()>. Фактически, Proto перегружаетвсехоператоров для вас, даже если они могут ничего не значить на вашем доменном языке. Это означает, что может быть возможно создать выражения, которые являются недействительными в вашем домене. Вы можете обнаружить недействительные выражения с помощью Proto, определивграмматикувашего доменного языка.

Для простоты предположим, что наш калькулятор EDSL должен разрешать только сложение, вычитание, умножение и деление. Любое выражение, связанное с любым другим оператором, является недействительным. Используя Proto, мы можем определить это требование, определив грамматику калькулятора EDSL. Это выглядит следующим образом:

// Define the grammar of calculator expressions
struct calculator_grammar
  : proto::or_<
        proto::plus< calculator_grammar, calculator_grammar >
      , proto::minus< calculator_grammar, calculator_grammar >
      , proto::multiplies< calculator_grammar, calculator_grammar >
      , proto::divides< calculator_grammar, calculator_grammar >
      , proto::terminal< proto::_ >
    >
{};

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

[Note]Note

Эта грамматика на самом деле немного свободнее, чем нам бы хотелось. Только заполнители и буквалы, которые конвертируются в дублеры, являются действительными терминалами. Позже мы увидим, как выразить подобные вещи в протограмматике.

После того, как вы определили грамматику EDSL, вы можете использовать метафункцию<proto::matches<>>, чтобы проверить, соответствует ли данный тип выражения грамматике. Например, мы можем добавить следующее к нашей перегрузке<calculator::operator()>:

template<typename Expr>
struct calculator
  : proto::extends< /* ... as before ... */ >
{
    /* ... */
    double operator()(double a1 = 0, double a2 = 0) const
    {
        // Check here that the expression we are about to
        // evaluate actually conforms to the calculator grammar.
        BOOST_MPL_ASSERT((proto::matches<Expr, calculator_grammar>));
        /* ... */
    }
};

Добавление строки<BOOST_MPL_ASSERT()>приводит к тому, что мы оцениваем только те выражения, которые соответствуют грамматике калькулятора EDSL. С грамматикой Proto<proto::matches<>>и<BOOST_MPL_ASSERT()>очень легко дать пользователям ваши короткие и читаемые ошибки времени компиляции EDSL, когда они случайно неправильно используют ваш EDSL.

[Note]Note

<BOOST_MPL_ASSERT()>является частью Библиотеки повышения метапрограммирования. Для этого достаточно<#include<boost/mpl/assert.hpp>>.

Controlling Operator Overloads

Грамматики и<proto::matches<>>позволяют обнаружить, когда пользователь создал недействительное выражение, и выпустить ошибку времени компиляции. Но что, если вы хотите помешать пользователям создавать недействительные выражения? Используя грамматику и домены вместе, вы можете отключить любую из перегрузок оператора Proto, которые создадут недействительное выражение. Это так же просто, как указать грамматику EDSL при определении домена, как показано ниже:

// Define a calculator domain. Expression within
// the calculator domain will be wrapped in the
// calculator<> expression wrapper.
// NEW: Any operator overloads that would create an
//      expression that does not conform to the
//      calculator grammar is automatically disabled.
struct calculator_domain
  : proto::domain< proto::generator<calculator>, calculator_grammar >
{};

Единственное, что мы изменили, это добавили<calculator_grammar>в качестве второго параметра шаблона к<proto::domain<>>шаблону при определении<calculator_domain>. С помощью этого простого дополнения мы отключаем любые перегрузки оператора Proto, которые создадут недействительное выражение калькулятора.

... And Much More

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

Счастливого метапрограммирования!

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

Как мы видели в примере с калькулятором из Введения, самый простой способ получить EDSL и запустить его - это просто определить некоторые терминалы следующим образом.

// Define a literal integer Proto expression.
proto::terminal<int>::type i = {0};
// This creates an expression template.
i + 1;

С некоторыми терминалами и перегрузками оператора Proto вы можете сразу же начать создавать шаблоны выражения.

Определение терминалов — с агрегированной инициализацией — иногда может быть немного неудобным. Proto обеспечивает более простую в использовании обертку для букв, которая может быть использована для построения терминальных выражений Protofied. Он называется<proto::literal<>>.

// Define a literal integer Proto expression.
proto::literal<int> i = 0;
// Proto literals are really just Proto terminal expressions.
// For example, this builds a Proto expression template:
i + 1;

Также существует функция<proto::lit()>для построения<proto::literal<>>на месте. Вышеуказанное выражение можно просто записать как:

// proto::lit(0) creates an integer terminal expression
proto::lit(0) + 1;

Как только у нас есть некоторые терминалы Proto, выражения, включающие эти терминалы, строят деревья экспрессии для нас. Proto определяет перегрузки для каждого из перегружаемых операторов C++ в пространстве имен<boost::proto>. Пока один операнд является выражением Прото, результатом операции является узел дерева, представляющий эту операцию.

[Note]Note

Операторские перегрузки Proto живут в пространстве имен<boost::proto>и обнаруживаются через ADL. Вот почему выражения должны быть «запятнаны» Прото-несностью, чтобы Прото мог строить деревья из выражений.

В результате перегрузок оператора Proto можно сказать:

-_1;        // OK, build a unary-negate tree node
_1 + 42;    // OK, build a binary-plus tree node

По большей части, это просто работает, и вам не нужно думать об этом, но некоторые операторы являются особенными, и может быть полезно знать, как Proto обрабатывает их.

Assignment, Subscript, and Function Call Operators

Прото также перегружает<operator=>,<operator[]>и<operator()>, но эти операторы являются функциями-членами шаблона выражения, а не свободными функциями в пространстве имён Прото. Ниже приведены действительные выражения Proto:

_1 = 5;     // OK, builds a binary assign tree node
_1[6];      // OK, builds a binary subscript tree node
_1();       // OK, builds a unary function tree node
_1(7);      // OK, builds a binary function tree node
_1(8,9);    // OK, builds a ternary function tree node
// ... etc.

Для первых двух строк, назначения и субскрипта, неудивительно, что результирующий узел выражения должен быть двоичным. В конце концов, в каждом выражении есть два операнда. Поначалу может показаться удивительным, что то, что кажется функцией вызова без аргументов<_1()>, фактически создает узел выражения с одним ребенком. Ребенок сам по себе<_1>. Также у выражения<_1(7)>двое детей:<_1>и<7>.

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

int i;
i = _1;         // ERROR: cannot assign _1 to an int
int *p;
p[_1];          // ERROR: cannot use _1 as an index
std::sin(_1);   // ERROR: cannot call std::sin() with _1

Кроме того, C++ имеет специальные правила для перегрузок<operator->>, которые делают его бесполезным для построения шаблонов выражения, поэтому Proto не перегружает его.

The Address-Of Operator

Proto перегружает адрес оператора для типов экспрессии, так что следующий код создает новый унарный адрес узла дерева:

&_1;    // OK, creates a unary address-of tree node

Онневозвращает адрес<_1>объекта. Однако в Proto есть специальный код, так что унарный адрес узла неявно конвертируется в указатель на его ребенка. Другими словами, следующий код работает и делает то, что вы можете ожидать, но не очевидным образом:

typedef
    proto::terminal< placeholder<0> >::type
_1_type;
_1_type const _1 = {{}};
_1_type const * p = &_1; // OK, &_1 implicitly converted

Если бы мы ограничивались только терминалами и перегрузками операторов, наши встроенные языки доменов не были бы очень выразительными. Представьте, что мы хотели расширить наш калькулятор EDSL с полным набором математических функций, таких как<sin()>и<pow()>, которые мы могли бы лениво вызвать следующим образом.

// A calculator expression that takes one argument
// and takes the sine of it.
sin(_1);

Мы хотели бы, чтобы вышеприведенное создало шаблон выражения, представляющий вызов функции. Когда это выражение оценивается, оно должно вызывать функцию. (По крайней мере, в этом смысл вызова функции, который мы хотели бы иметь в калькуляторе EDSL.) Вы можете определить<sin>следующим образом:

// "sin" is a Proto terminal containing a function pointer
proto::terminal< double(*)(double) >::type const sin = {&std::sin};

В приведенном выше описании мы определяем<sin>как терминал Proto, содержащий указатель на функцию<std::sin()>. Теперь мы можем использовать<sin>в качестве ленивой функции.<default_context>, который мы видели во Введении, знает, как оценивать ленивые функции. Рассмотрим следующее:

double pi = 3.1415926535;
proto::default_context ctx;
// Create a lazy "sin" invocation and immediately evaluate it
std::cout << proto::eval( sin(pi/2), ctx ) << std::endl;

Вышеприведенный код распечатывает:

1

Я не специалист по тригонометрии, но мне это кажется правильным.

Мы можем написать<sin(pi/2)>, потому что<sin>объект, который является терминалом Proto, имеет перегруженный<operator()()>, который строит узел, представляющий функцию вызова вызова. Тип<sin(pi/2)>на самом деле таков:

// The type of the expression sin(pi/2):
proto::function<
    proto::terminal< double(*)(double) >::type const &
    proto::result_of::as_child< double const >::type
>::type

Этот тип дополнительно расширяется до неприглядного типа узла с тегомтипа<proto::tag::function>и двумя детьми: первый представляет функцию, на которую следует ссылаться, а второй представляет аргумент функции. (Таги узлов описывают операцию, которая создала узел.) Разница между<a +b>и<a- b>заключается в том, что первый имеет тип тега<proto::tag::plus>, а второй имеет тип тега<proto::tag::minus>. Типы тегов - это чистая информация о времени компиляции.

[Note]Note

При вычислении типов<proto::result_of::as_child<>>является метафункцией, которая гарантирует, что ее аргумент является типом экспрессии Прото. Если это не так, он становится терминалом Proto. Мы узнаем больше об этой метафункции, вместе с<proto::as_child()>, ее аналогом во время выполнения,позже. На данный момент вы можете забыть об этом.

Важно отметить, что в терминалах, содержащих указатели функций, нет ничего особенного.ЛюбойВыражение прото имеет перегруженную функцию оператора вызова. Подумайте:

// This compiles!
proto::lit(1)(2)(3,4)(5,6,7,8);

Сначала это может показаться странным. Он создает целочисленный терминал с<proto::lit()>, а затем снова и снова вызывает его как функцию. Что это значит? Кто знает?! Вы можете решить, когда вы определяете контекст оценки или преобразование. Но об этом позже.

Making Lazy Functions, Continued

Что, если мы хотим добавить функцию<pow()>в наш калькулятор EDSL, которую пользователи могли бы вызвать следующим образом?

// A calculator expression that takes one argument
// and raises it to the 2nd power
pow< 2 >(_1);

Описанная выше простая техника создания<pow>терминала, содержащего указатель функции, здесь не работает. Если<pow>является объектом, то выражение<pow< 2>(_1)>не является действительным C++. (Ну, технически это так; это означает<pow>меньше, чем 2, больше, чем<(_1)>, что совсем не похоже на то, что мы хотим.)<pow>должен быть реальным шаблоном функций. Но это должна быть необычная функция: та, которая возвращает шаблон выражения.

С<sin>мы полагались на Proto, чтобы обеспечить перегруженный<operator()()>, чтобы построить узел выражения с типом тега<proto::tag::function>для нас. Теперь мы должны сделать это сами. Как и раньше, у узла будет двое детей: функция вызова и аргумент функции.

С<sin>функция вызова была необработанным указателем функции, завернутым в терминал Proto. В случае<pow>мы хотим, чтобы это был терминал, содержащий объект функции в стиле TR1. Это позволит нам параметризировать функцию на экспоненте. Ниже приведена реализация простой обертки в стиле TR1 для функции<std::pow>:

// Define a pow_fun function object
template< int Exp >
struct pow_fun
{
    typedef double result_type;
    double operator()(double d) const
    {
        return std::pow(d, Exp);
    }
};

Следуя примеру<sin>, мы хотим<pow< 1>( pi/2)>иметь такой тип:

// The type of the expression pow<1>(pi/2):
proto::function<
    proto::terminal< pow_fun<1> >::type
    proto::result_of::as_child< double const >::type
>::type

Мы могли бы написать функцию<pow()>, используя такой код, но она многословна и подвержена ошибкам; слишком легко ввести тонкие ошибки, забыв позвонить<proto::as_child()>, где это необходимо, в результате чего код, который, кажется, работает, но иногда не работает. Proto обеспечивает лучший способ построения экспрессионных узлов:<proto::make_expr()>.

Lazy Functions Made Simple With make_expr()

Proto помогает создавать шаблоны выражения под названием<proto::make_expr()>. Мы можем кратко определить функцию<pow()>с ней как ниже.

// Define a lazy pow() function for the calculator EDSL.
// Can be used as: pow< 2 >(_1)
template< int Exp, typename Arg >
typename proto::result_of::make_expr<
    proto::tag::function  // Tag type
  , pow_fun< Exp >        // First child (by value)
  , Arg const &           // Second child (by reference)
>::type const
pow(Arg const &arg)
{
    return proto::make_expr<proto::tag::function>(
        pow_fun<Exp>()    // First child (by value)
      , boost::ref(arg)   // Second child (by reference)
    );
}

Есть некоторые вещи, которые следует отметить в вышеупомянутом коде. Мы используем<proto::result_of::make_expr<>>для расчета типа возврата. Первый параметр шаблона - это тип тега для узла выражения, который мы строим - в данном случае<proto::tag::function>.

Последующие параметры шаблона<proto::result_of::make_expr<>>представляют собой дочерние узлы. Если тип ребенка еще не является выражением Прото, он автоматически превращается в терминал с<proto::as_child()>. Тип, такой как<pow_fun<Exp>>, приводит к терминалу, который удерживается значением, тогда как тип, такой как<Arg const&>(обратите внимание на ссылку), указывает, что результат должен удерживаться ссылкой.

В теле функции находится вызов времени выполнения<proto::make_expr()>. Это близко отражает расчет типа возврата.<proto::make_expr()>требует, чтобы вы указали тип тега узла в качестве параметра шаблона. Аргументами функции становятся дети узла. Когда ребенка нужно хранить по ценности, ничего особенного делать не надо. Когда ребенок должен храниться по ссылке, вы должны использовать функцию<boost::ref()>, чтобы обернуть аргумент.

И это все!<proto::make_expr()>- это способ ленивого человека сделать ленивую забаву.

В этом разделе мы узнаем все одоменах. В частности, мы узнаем:

  • Как связать выражения Proto с доменом
  • Как добавить членов в выражения в домене
  • Как использовать генератордля постобработки всех новых выражений, созданных в вашем домене,
  • Как контролировать, какие операторы перегружены в домене
  • Как определить политику захвата для выражений детей и объектов, не являющихся прото, и
  • Как сделать выражения из отдельных доменов взаимодействующими

В разделеHello Calculatorмы рассмотрели возможность непосредственного использования выражений калькулятора в качестве лямбда-выражений в вызовах алгоритмов STL, как показано ниже:

double data[] = {1., 2., 3., 4.};
// Use the calculator EDSL to square each element ... HOW?
std::transform( data, data + 4, data, _1 * _1 );

Сложность, если вы помните, заключалась в том, что по умолчанию выражения Proto не имеют собственного интересного поведения. Это просто деревья. В частности, выражение<_1 *_1>не будет иметь<operator()>, которое принимает двойной и возвращает двойной, как<std::transform()>ожидает, если мы не дадим ему один. Чтобы сделать эту работу, нам нужно было определить тип обертки экспрессии, который определял функцию члена<operator()>, и нам нужно было связать обертку с калькулятором.

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

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

Первый шаг к тому, чтобы дать калькулятору дополнительные выражения поведения, - это определить область калькулятора. Все выражения в области калькулятора будут проникнуты калькуляторностью, как мы увидим.

// A type to be used as a domain tag (to be defined below)
struct calculator_domain;

Мы используем этот тип домена при расширении типа<proto::expr<>>, что мы делаем с шаблоном класса<proto::extends<>>. Вот наша обертка для выражения, которая наполняет выражение калькулятором. Это описано ниже.

// The calculator<> expression wrapper makes expressions
// function objects.
template< typename Expr >
struct calculator
  : proto::extends< Expr, calculator< Expr >, calculator_domain >
{
    typedef
        proto::extends< Expr, calculator< Expr >, calculator_domain >
    base_type;
    calculator( Expr const &expr = Expr() )
      : base_type( expr )
    {}
    // This is usually needed because by default, the compiler-
    // generated assignment operator hides extends<>::operator=
    BOOST_PROTO_EXTENDS_USING_ASSIGN(calculator)
    typedef double result_type;
    // Hide base_type::operator() by defining our own which
    // evaluates the calculator expression with a calculator context.
    result_type operator()( double d1 = 0.0, double d2 = 0.0 ) const
    {
        // As defined in the Hello Calculator section.
        calculator_context ctx;
        // ctx.args is a vector<double> that holds the values
        // with which we replace the placeholders (e.g., _1 and _2)
        // in the expression.
        ctx.args.push_back( d1 ); // _1 gets the value of d1
        ctx.args.push_back( d2 ); // _2 gets the value of d2
        return proto::eval(*this, ctx ); // evaluate the expression
    }
};

Мы хотим, чтобы выражения калькулятора были функциональными объектами, поэтому мы должны определить<operator()>, который принимает и возвращает удваивается. С помощью<proto::extends<>>обертки выше это делает<calculator<>>шаблон. Первый шаблон к<proto::extends<>>параметру — это тип выражения, который мы расширяем. Второй — это тип завернутого выражения. Третий параметр — это домен, с которым связана эта обертка. Тип обертки, такой как<calculator<>>, который наследует от<proto::extends<>>, ведет себя так же, как тип выражения, который он расширил, с любым дополнительным поведением, которое вы решите дать ему.

[Note]Note

Почему бы не наследовать от<proto::expr<>>?

Вы можете подумать, что этот бизнес по расширению выражения излишне сложен. Почему C++ поддерживает наследование? Почему<calculator<Expr>>не может просто наследовать от<Expr>напрямую? Причина в том, что<Expr>, который предположительно является инстанциацией<proto::expr<>>, имеет перегрузки оператора построения шаблонов экспрессии, которые будут неверными для производных типов. Они будут хранить<*this>со ссылкой на<proto::expr<>>, эффективно отрезая любые производные части.<proto::extends<>>дает оператору производных типов перегрузки, которые не срезают дополнительных участников.

Хотя это и не является строго необходимым в данном случае, мы вводим<extends<>::operator=>в область применения макроса<BOOST_PROTO_EXTENDS_USING_ASSIGN()>. Это действительно необходимо, только если вы хотите, чтобы такие выражения, как<_1 =3>, создавали лениво оцененное задание.<proto::extends<>>определяет подходящий<operator=>для вас, но созданный компилятором<calculator<>::operator=>будет скрывать его, если вы не сделаете его доступным с помощью макроса.

Обратите внимание, что при реализации<calculator<>::operator()>мы оцениваем выражение с<calculator_context>, которое мы определили ранее. Как мы видели ранее, контекст — это то, что придает операторам их значение. В случае калькулятора контекст также определяет значение терминалов заполнителя.

Теперь, когда мы определили экспрессионную обертку<calculator<>>, нам нужно обернуть заполнители, чтобы наполнить их калькулятором:

calculator< proto::terminal< placeholder<0> >::type > const _1;
calculator< proto::terminal< placeholder<1> >::type > const _2;
Retaining POD-ness with BOOST_PROTO_EXTENDS()

Чтобы использовать<proto::extends<>>, ваш тип расширения должен исходить из<proto::extends<>>. К сожалению, это означает, что ваш тип расширения больше не является POD, и его экземпляры не могут бытьстатически инициализированы. (См. разделСтатическая инициализацияв приложенииОбоснование, почему это важно.) В частности, как определено выше, глобальные объекты-заполнители<_1>и<_2>должны быть инициализированы во время выполнения, что может привести к тонкому порядку ошибок инициализации.

Есть еще один способ сделать расширение выражения, которое не приносит в жертву POD-несущность: макрос<BOOST_PROTO_EXTENDS>. Вы можете использовать его так же, как вы используете<proto::extends<>>. Мы можем использовать<BOOST_PROTO_EXTENDS>(), чтобы сохранить<calculator<>>POD и наши заполнители статически инициализированы.

// The calculator<> expression wrapper makes expressions
// function objects.
template< typename Expr >
struct calculator
{
    // Use BOOST_PROTO_EXTENDS() instead of proto::extends<> to
    // make this type a Proto expression extension.
    BOOST_PROTO_EXTENDS(Expr, calculator<Expr>, calculator_domain)
    typedef double result_type;
    result_type operator()( double d1 = 0.0, double d2 = 0.0 ) const
    {
        /* ... as before ... */
    }
};

С новым типом<calculator<>>мы можем переопределить наши заполнители для статической инициализации:

calculator< proto::terminal< placeholder<0> >::type > const _1 = {{{}}};
calculator< proto::terminal< placeholder<1> >::type > const _2 = {{{}}};

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

Что делает<BOOST_PROTO_EXTENDS>? Он определяет элемент данных расширяемого типа выражения; некоторые вложенные типдефы, которые требуются Proto;<operator=>,<operator[]>и<operator()>перегрузки для построения шаблонов выражения; и вложенный<result<>>шаблон для расчета типа возврата<operator()>. Однако в этом случае перегрузки<operator()>и шаблон<result<>>не нужны, потому что мы определяем наш собственный<operator()>в<calculator<>>типе. Proto предоставляет дополнительные макросы для более точного контроля над функциями членов. Мы могли бы улучшить наш<calculator<>>тип следующим образом:

// The calculator<> expression wrapper makes expressions
// function objects.
template< typename Expr >
struct calculator
{
    // Use BOOST_PROTO_BASIC_EXTENDS() instead of proto::extends<> to
    // make this type a Proto expression extension:
    BOOST_PROTO_BASIC_EXTENDS(Expr, calculator<Expr>, calculator_domain)
    // Define operator[] to build expression templates:
    BOOST_PROTO_EXTENDS_SUBSCRIPT()
    // Define operator= to build expression templates:
    BOOST_PROTO_EXTENDS_ASSIGN()
    typedef double result_type;
    result_type operator()( double d1 = 0.0, double d2 = 0.0 ) const
    {
        /* ... as before ... */
    }
};

Обратите внимание, что теперь мы используем<BOOST_PROTO_BASIC_EXTENDS>()вместо<BOOST_PROTO_EXTENDS>(). Это просто добавляет элемент данных и вложенные типдефы, но не любой из перегруженных операторов. Они добавляются отдельно<BOOST_PROTO_EXTENDS_ASSIGN>()и<BOOST_PROTO_EXTENDS_SUBSCRIPT>(). Мы исключаем оператор вызова функций и вложенный шаблон<result<>>, который мог быть определен с макросом Proto<BOOST_PROTO_EXTENDS_FUNCTION>() [316).

Итак, вот макросы, которые вы можете использовать для определения расширений выражений, и краткое описание каждого из них.

Table 29.2. Expression Extension Macros

Макро

Цель

BOOST_PROTO_BASIC_EXTENDS(
    expression
  , extension
  , domain
)

Определяет элемент данных типа<expression>и некоторые вложенные типдефы, которые требуются Proto.

<BOOST_PROTO_EXTENDS_ASSIGN>()

Определяет<operator=>. Только тогда, когда ему предшествует<BOOST_PROTO_BASIC_EXTENDS>

.

<BOOST_PROTO_EXTENDS_SUBSCRIPT>[]

Определяет<operator[]>. Только тогда, когда ему предшествует<BOOST_PROTO_BASIC_EXTENDS>

.

<BOOST_PROTO_EXTENDS_FUNCTION>

Определяет<operator()>и вложенный<result<>>шаблон для расчета типа возврата. Только тогда, когда ему предшествует<BOOST_PROTO_BASIC_EXTENDS>

.

BOOST_PROTO_EXTENDS(
    expression
  , extension
  , domain
)

Equivalent to:

BOOST_PROTO_BASIC_EXTENDS(expression, extension, domain)
  BOOST_PROTO_EXTENDS_ASSIGN()
  BOOST_PROTO_EXTENDS_SUBSCRIPT()
  BOOST_PROTO_EXTENDS_FUNCTION()


[Warning]Warning

Аргументно-зависимый поиск и<BOOST_PROTO_EXTENDS>

Перегрузки оператора Proto определяются в пространстве имен<boost::proto>и обнаруживаются в зависимости от аргумента поиска (ADL). Обычно это работает только потому, что выражения состоят из типов, которые живут в пространстве имен<boost::proto>. Однако иногда, когда вы используете<BOOST_PROTO_EXTENDS>(), это не так. Подумайте:

template<class T>
struct my_complex
{
    BOOST_PROTO_EXTENDS(
        typename proto::terminal<std::complex<T> >::type
      , my_complex<T>
      , proto::default_domain
    )
};
int main()
{
    my_complex<int> c0, c1;
    c0 + c1; // ERROR: operator+ not found
}

Проблема связана с тем, как работает аргументированный поиск. Тип<my_complex<int>>никак не связан с пространством имен<boost::proto>, поэтому операторы, определенные там, не рассматриваются. (Если бы мы унаследовали от<proto::extends<>>вместо использования<BOOST_PROTO_EXTENDS>(), мы бы избежали проблемы, потому что наследования от типа в<boost::proto>пространстве имен достаточно, чтобы получить ADL, чтобы начать.)

Так что мы можем сделать? Добавив дополнительный параметр шаблона, который по умолчанию соответствует типу в пространстве имен<boost::proto>, мы можем обмануть ADL, чтобы найти правильные перегрузки оператора. Решение выглядит так:

template<class T, class Dummy = proto::is_proto_expr>
struct my_complex
{
    BOOST_PROTO_EXTENDS(
        typename proto::terminal<std::complex<T> >::type
      , my_complex<T>
      , proto::default_domain
    )
};
int main()
{
    my_complex<int> c0, c1;
    c0 + c1; // OK, operator+ found now!
}

Тип<proto::is_proto_expr>не что иное, как пустая структура, но, сделав ее параметром шаблона, мы делаем<boost::proto>ассоциированным пространством имен<my_complex<int>>. Теперь ADL может успешно обнаруживать перегрузки оператора Proto.

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

// Define the calculator_domain we forward-declared above.
// Specify that all expression in this domain should be wrapped
// in the calculator<> expression wrapper.
struct calculator_domain
  : proto::domain< proto::generator< calculator > >
{};

Первым параметром шаблона<proto::domain<>>является генератор. «Генератор» — это просто причудливое название функционального объекта, который принимает выражение и что-то с ним делает.<proto::generator<>>очень простой — он заворачивает выражение в обертку, которую вы указываете.<proto::domain<>>наследует от своего параметра генератора, поэтому все домены сами являются функциональными объектами.

Если мы используем<BOOST_PROTO_EXTENDS>()для сохранения нашего типа расширения экспрессии POD, то нам нужно использовать<proto::pod_generator<>>вместо<proto::generator<>>следующим образом:

// If calculator<> uses BOOST_PROTO_EXTENDS() instead of 
// use proto::extends<>, use proto::pod_generator<> instead
// of proto::generator<>.
struct calculator_domain
  : proto::domain< proto::pod_generator< calculator > >
{};

После того, как Proto вычислил новый тип выражения, он проверяет домены детских выражений. Они должны совпадать. Предполагая, что они это сделают, Proto создает новое выражение и передает его<Domain>::оператор[]для любой дополнительной обработки. Если мы не укажем генератор, новое выражение будет пропущено без изменений. Но поскольку мы указали генератор выше,<calculator_domain::operator()>возвращает<calculator<>>объектов.

Теперь мы можем использовать выражения калькулятора в качестве объектов функций для алгоритмов STL следующим образом:

double data[] = {1., 2., 3., 4.};
// Use the calculator EDSL to square each element ... WORKS! :-)
std::transform( data, data + 4, data, _1 * _1 );

По умолчанию Proto определяет все возможные операторские перегрузки для Protofied-выражений. Это позволяет легко объединить EDSL. В некоторых случаях, однако, наличие беспорядочных перегрузок Прото может привести к путанице или еще хуже. Когда это произойдет, вам придется отключить некоторых перегруженных операторов Proto. Это делается путем определения грамматики для вашего домена и указания ее в качестве второго параметра шаблона<proto::domain<>>.

В разделеHello Calculatorмы увидели пример грамматики Прото, который повторяется здесь:

// Define the grammar of calculator expressions
struct calculator_grammar
  : proto::or_<
        proto::plus< calculator_grammar, calculator_grammar >
      , proto::minus< calculator_grammar, calculator_grammar >
      , proto::multiplies< calculator_grammar, calculator_grammar >
      , proto::divides< calculator_grammar, calculator_grammar >
      , proto::terminal< proto::_ >
    >
{};

У нас будет гораздо больше информации о грамматике в последующих разделах, но пока мы просто скажем, что структура<calculator_grammar>описывает подмножество всех типов выражений — подмножество, которое содержит действительные выражения калькулятора. Мы хотели бы запретить Прото создавать калькуляторное выражение, не соответствующее этой грамматике. Мы делаем это, изменяя определение структуры<calculator_domain>.

// Define the calculator_domain. Expressions in the calculator
// domain are wrapped in the calculator<> wrapper, and they must
// conform to the calculator_grammar:
struct calculator_domain
  : proto::domain< proto::generator< calculator >, calculator_grammar  >
{};

Единственным новым дополнением является<calculator_grammar>в качестве второго параметра шаблона<proto::domain<>>. Это приводит к отключению любой из перегрузок оператора Proto, что создает недействительное выражение калькулятора.

Другим распространенным использованием этой функции будет отключение унарной перегрузки Proto<operator&>. Для пользователей EDSL может быть удивительно, что они не могут взять адрес своего выражения! Вы можете очень легко отключить перегрузку Proto<operator&>для вашего домена с помощью очень простой грамматики, как показано ниже:

// For expressions in my_domain, disable Proto's
// unary address-of operator.
struct my_domain
  : proto::domain<
        proto::generator< my_wrapper >
        // A simple grammar that matches any expression that
        // is not a unary address-of expression.
      , proto::not_< proto::address_of< _ > >
    >
{};

Тип<proto::not_< proto::address_of< _> >>— очень простая грамматика, которая соответствует всем выражениям, кроме унарных адресов выражений. В разделе, описывающем промежуточную форму Прото, мы расскажем гораздо больше о грамматике.

[Note]Note

Это продвинутая тема. Не стесняйтесь пропустить это, если вы только начинаете с Proto.

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

Primer: as_child vs. as_expr

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

<proto::as_expr()>обычно используетсявы, чтобы превратить объект в выражение Прото, которое должно содержаться в локальной переменной, так как:

auto l = proto::as_expr(x); // Turn x into a Proto expression, hold the result in a local

Вышеизложенное работает независимо от того, является ли<x>уже выражением Прото или нет. Объект<l>гарантированно является действительным выражением Прото. Если<x>является непротообъектом, он превращается в терминальное выражение, которое удерживает<x>по значению.<#include <boost/proto/core.hpp>>Если<x>уже является объектом Прото,<proto::as_expr()>возвращает егопо значениюнеизмененным.

Напротив,<proto::as_child()>используется внутри Proto для предварительной обработки объектов, прежде чем сделать их детьми другого выражения. Поскольку он является внутренним для Proto, вы не видите его явно, но он есть за кулисами в таких выражениях:

x + y; // Consider that y is a Proto expression, but x may or may not be.

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

В приведенной ниже таблице кратко изложено вышеизложенное описание.

Table 29.3. proto::as_expr() vs. proto::as_child()

Функция

454Когда<t>не является прото-экспром...

Когда<t>является прото-экспром...

<proto::as_expr(t)>

Возврат (по стоимости) нового держателя терминала Proto<t>по стоимости.

Возврат<t>по стоимости без изменения.

<proto::as_child(t)>

Возврат (по стоимости) нового держателя терминала Proto<t>по ссылке.


[Note]Note

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

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

proto::literal<int> i(0);
auto l = i + 42; // This is WRONG! Don't do this.

Почему это неправильно? Потому что<proto::as_child()>превратит целое число 42 в терминал Прото, который содержит ссылку на временное целое число, инициализированное с 42. Время жизни этого временного числа заканчивается в полуколоне, гарантируя, что местный<l>останется с болтающейся ссылкой на целое число умерших. Что делать? Один из ответов — использовать<proto::deep_copy()>. Другой способ — настроить поведение<proto::as_child()>для вашего домена. Читайте дальше для деталей.

Per-Domain as_child

Чтобы контролировать, как Proto строит выражения из подвыражений в вашем домене, определите свой домен как обычно, а затем определите вложенный шаблон класса<as_child<>>в нем следующим образом:

class my_domain
  : proto::domain< my_generator, my_grammar >
{
    // Here is where you define how Proto should handle
    // sub-expressions that are about to be glommed into
    // a larger expression.
    template< typename T >
    struct as_child
    {
        typedef unspecified-Proto-expr-type result_type;
        result_type operator()( T & t ) const
        {
            return unspecified-Proto-expr-object;
        }
    };
};

Следует отметить одну важную вещь: в приведенном выше коде параметр шаблона<T>может быть или не быть типом выражения Proto, но результатдолженбыть типом выражения Proto или ссылкой на него. Это означает, что большинство шаблонов, определяемых пользователем<as_child<>>, должны проверить, является ли<T>выражением или нет (с использованием<proto::is_expr<>>), а затем превратить невыражения в терминалы Proto, обернув их как<proto::terminal</* ... */ >::type>или эквивалент.

Per-Domain as_expr

Хотя это менее распространено, Proto также позволяет настраивать поведение<proto::as_expr()>на основе каждого домена. Техника идентична таковой для<as_child>. См. ниже:

class my_domain
  : proto::domain< my_generator, my_grammar >
{
    // Here is where you define how Proto should handle
    // objects that are to be turned into expressions
    // fit for storage in local variables.
    template< typename T >
    struct as_expr
    {
        typedef unspecified-Proto-expr-type result_type;
        result_type operator()( T & t ) const
        {
            return unspecified-Proto-expr-object;
        }
    };
};
Making Proto Expressions auto-safe

Давайте еще раз рассмотрим проблему, описанную выше, связанную с ключевым словом C++11<auto>и поведением по умолчанию<proto::as_child()>.

proto::literal<int> i(0);
auto l = i + 42; // This is WRONG! Don't do this.

Напомним, что проблема заключается в сроке службы временного целого числа, созданного для удержания значения 42. Местный<l>останется с намеком на него после того, как его жизнь закончится. Что, если мы хотим, чтобы Proto делал выражения безопасными для хранения в локальных переменных? Мы можем сделать это очень легко, заставив<proto::as_child()>вести себя так же, как<proto::as_expr()>. Это достигается следующим кодом:

template< typename E >
struct my_expr;
struct my_generator
  : proto::pod_generator< my_expr >
{};
struct my_domain
  : proto::domain< my_generator >
{
     // Make as_child() behave like as_expr() in my_domain.
     // (proto_base_domain is a typedef for proto::domain< my_generator >
     // that is defined in proto::domain<>.)
     template< typename T >
     struct as_child
       : proto_base_domain::as_expr< T >
     {};
};
template< typename E >
struct my_expr
{
    BOOST_PROTO_EXTENDS( E, my_expr< E >, my_domain )
};
/* ... */
proto::literal< int, my_domain > i(0);
auto l = i + 42; // OK! Everything is stored by value here.

Обратите внимание, что<my_domain::as_child<>>просто откладывается от реализации по умолчанию<as_expr<>>, найденной в<proto::domain<>>. Путем простого перекрестного подключения нашего домена<as_child<>>к<as_expr<>>мы гарантируем, что все терминалы, которые могут удерживаться стоимостью, являются, и что все выражения ребенка также удерживаются стоимостью. Это увеличивает копирование и может повлечь за собой затраты на производительность во время выполнения, но это устраняет любые проблемы управления сроком службы.

В другом примере см. определение<lldomain>в<libs/proto/example/lambda.hpp>. Этот пример является полным повторением Библиотеки Boost Lambda (BLL) на вершине Boost. Объекты функции, генерируемые BLL, безопасны для хранения в локальных переменных. Чтобы эмулировать это с помощью Proto,<lldomain>кросс-провода<as_child<>>до<as_expr<>>, как указано выше, но с одним дополнительным поворотом: объекты с типом массива также хранятся по ссылке. Посмотри.

[Note]Note

Это продвинутая тема. Не стесняйтесь пропустить это, если вы только начинаете с Proto.

Способностьсоставлятьразличные EDSL является одной из их самых захватывающих особенностей. Подумайте, как вы строите парсер с использованием yacc. Вы пишете свои правила грамматики на доменном языке yacc. Затем вы встраиваете семантические действия, написанные на C, в свою грамматику. Парсерный генератор Boost Spirit дает вам такую же возможность. Вы пишете грамматические правила, используя Дух. Ци и встраивание семантических действий с помощью библиотеки Феникса. Phoenix и Spirit являются прото-ориентированными доменными языками со своим собственным синтаксисом и семантикой. Но вы можете свободно встраивать выражения Феникса в выражения Духа. В этом разделе описана функция поддомена Proto, которая позволяет определять семейства взаимодействующих доменов.

Dueling Domains

Когда вы пытаетесь создать выражение из двух подвыражений в разных доменах, что такое домен полученного выражения? Это основная проблема, которая решается поддоменами. Рассмотрим следующий код:

#include <boost/proto/proto.hpp>
namespace proto = boost::proto;
// Forward-declare two expression wrappers
template<typename E> struct spirit_expr;
template<typename E> struct phoenix_expr;
// Define two domains
struct spirit_domain  : proto::domain<proto::generator<spirit_expr> > {};
struct phoenix_domain : proto::domain<proto::generator<phoenix_expr> > {};
// Implement the two expression wrappers
template<typename E>
struct spirit_expr
  : proto::extends<E, spirit_expr<E>, spirit_domain>
{
    spirit_expr(E const &e = E()) : spirit_expr::proto_extends(e) {}
};
template<typename E>
struct phoenix_expr
  : proto::extends<E, phoenix_expr<E>, phoenix_domain>
{
    phoenix_expr(E const &e = E()) : phoenix_expr::proto_extends(e) {}
};
int main()
{
    proto::literal<int, spirit_domain> sp(0);
    proto::literal<int, phoenix_domain> phx(0);
    // Whoops! What does it mean to add two expressions in different domains?
    sp + phx; // ERROR
}

Выше мы определяем два домена, называемые<spirit_domain>и<phoenix_domain>, и объявляем два буквальных обозначения в каждом. Затем мы пытаемся составить их в более крупное выражение с помощью оператора двоичного плюса Proto, и это не удается. Прото не может понять, должно ли полученное выражение быть в области Духа или в области Феникса, и, таким образом, должно ли оно быть примером<spirit_expr<>>или<phoenix_expr<>>. Мы должны рассказать Прото, как разрешить конфликт. Мы можем сделать это, объявив, что Феникс является поддоменом Духа, как в следующем определении<phoenix_domain>:

// Declare that phoenix_domain is a sub-domain of spirit_domain
struct phoenix_domain
  : proto::domain<proto::generator<phoenix_expr>, proto::_, spirit_domain>
{};

Третий параметр шаблона<proto::domain<>>— супердомен. Определяя<phoenix_domain>выше, мы говорим, что выражения Феникса могут быть объединены с выражениями Духа, и что, когда это происходит, результирующее выражение должно быть выражением Духа.

[Note]Note

Если вам интересно, какова цель<proto::_>в определении<phoenix_domain>выше, помните, что второй параметр шаблона к<proto::domain<>>является грамматикой домена.& #8220;<proto::_>& #8221;является по умолчанию и означает, что домен не накладывает никаких ограничений на выражения, которые действительны в нем.

Domain Resolution

Когда в заданном выражении задействовано несколько доменов, Proto использует некоторые правила, чтобы выяснить, какой домен «выигрывает». Правила слабо смоделированы на правилах наследования C++.<Phoenix_domain>является поддоменом<spirit_domain>. Вы можете сравнить это с производными / базовыми отношениями, которые придают выражениям Феникса неявное преобразование в выражения Духа. И поскольку выражения Феникса могут быть «превращены» в выражения Духа, они могут свободно сочетаться с выражениями Духа, и результатом является выражение Духа.

[Note]Note

Супер- и поддомены фактически не реализуются с использованием наследования. Это только полезная ментальная модель.

Аналогия с наследованием имеет место даже в случае трех доменов, когда два являются поддоменами третьего. Представьте себе другой домен под названием<foobar_domain>, который также был поддоменом<spirit_domain>. Выражения в<foobar_domain>могут быть объединены с выражениями в<phoenix_domain>, и результирующее выражение будет в<spirit_domain>. Это потому, что выражения в двух поддоменах имеют «конверсии» в супердомен, поэтому операция разрешена, и супердомен выигрывает.

The Default Domain

Когда вы не присваиваете выражение Proto определенному домену, Proto считает его членом так называемого домена по умолчанию<proto::default_domain>. Даже объекты, не являющиеся Proto, рассматриваются как терминалы в домене по умолчанию. Подумайте:

int main()
{
    proto::literal<int, spirit_domain> sp(0);
    // Add 1 to a spirit expression. Result is a spirit expression.
    sp + 1;
}

Выражения в домене по умолчанию (или невыражения, такие как<1>) имеют своего рода неявное преобразование в выражения любого другого типа домена. Более того, вы можете определить свой домен как поддомен домена по умолчанию. При этом вы даете выражения в ваших конверсиях домена в выражения в любом другом домене. Это как& #8220;свободная любовь& #8221;домен, потому что он будет свободно смешиваться со всеми другими доменами.

Давайте еще раз подумаем о Phoenix EDSL. Поскольку он обеспечивает в целом полезную лямбда-функциональность, разумно предположить, что многие другие EDSL, помимо Spirit, могут захотеть встроить выражения Phoenix. Иными словами,<phoenix_domain>должно быть поддоменом<proto::default_domain>, а не<spirit_domain>:

// Declare that phoenix_domain is a sub-domain of proto::default_domain
struct phoenix_domain
  : proto::domain<proto::generator<phoenix_expr>, proto::_, proto::default_domain>
{};

Это намного лучше. Выражения Феникса теперь можно ставить где угодно.

Sub-Domain Summary

Используйте поддомены Proto, чтобы смешивать выражения из нескольких доменов. И если вы хотите, чтобы выражения в вашем домене свободно комбинировались свсемивыражениями, сделайте его поддоменом<proto::default_domain>.

Предыдущие обсуждения определения передних концов Proto сделали большое предположение: у вас есть роскошь определять все с нуля. Что произойдет, если у вас есть существующие типы, например, тип матрицы и векторный тип, которые вы хотели бы рассматривать как прото-терминалы? Прото обычно торгуется только в своих собственных типах выражений, но с<BOOST_PROTO_DEFINE_OPERATORS>(), он также может размещать ваши пользовательские типы терминалов.

Допустим, например, что у вас есть следующие типы и что вы не можете изменить их, чтобы сделать их& #8220;родные& #8221;типы терминалов Proto.

namespace math
{
    // A matrix type ...
    struct matrix { /*...*/ };
    // A vector type ...
    struct vector { /*...*/ };
}

Вы можете ненавязчиво создавать объекты этих типов терминалов Proto, определяя соответствующие операторские перегрузки с помощью<BOOST_PROTO_DEFINE_OPERATORS>(). Основная процедура заключается в следующем:

  1. Определите черту, которая возвращается истинной для ваших типов и ложной для всех остальных.
  2. Откройте пространство имен ваших типов и используйте<BOOST_PROTO_DEFINE_OPERATORS>()для определения набора перегрузок оператора, передав имя признака в качестве первого макропараметра и имя домена Proto (например,<proto::default_domain>) в качестве второго.

Следующий код показывает, как это работает.

namespace math
{
    template<typename T>
    struct is_terminal
      : mpl::false_
    {};
    // OK, "matrix" is a custom terminal type
    template<>
    struct is_terminal<matrix>
      : mpl::true_
    {};
    // OK, "vector" is a custom terminal type
    template<>
    struct is_terminal<vector>
      : mpl::true_
    {};
    // Define all the operator overloads to construct Proto
    // expression templates, treating "matrix" and "vector"
    // objects as if they were Proto terminals.
    BOOST_PROTO_DEFINE_OPERATORS(is_terminal, proto::default_domain)
}

Вызыв макроса<BOOST_PROTO_DEFINE_OPERATORS>определяет полный набор операторских перегрузок, которые обрабатывают<matrix>и<vector>объекты, как если бы они были терминалами Proto. И поскольку операторы определены в том же пространстве имен, что и типы<matrix>и<vector>, операторы будут найдены путем поиска, зависящего от аргументов. С приведенным выше кодом мы теперь можем создавать шаблоны экспрессии с матрицами и векторами, как показано ниже.

math::matrix m1;
math::vector v1;
proto::literal<int> i(0);
m1 * 1;  // custom terminal and literals are OK
m1 * i;  // custom terminal and Proto expressions are OK
m1 * v1; // two custom terminals are OK, too.

Иногда, чтобы сделать жизнь ваших пользователей легкой, вы должны сделать свою собственную жизнь трудной. Предоставление пользователям естественного и гибкого синтаксиса часто включает в себя написание большого количества повторяющихся перегрузок функций. Этого может быть достаточно, чтобы получить повторяющиеся стрессовые травмы! Прежде чем навредить себе, ознакомьтесь с макросами, которые Proto предоставляет для автоматизации многих повторяющихся задач по генерации кода.

Представьте, что мы пишем лямбда EDSL, и мы хотели бы включить синтаксис для построения временных объектов любого типа с использованием следующего синтаксиса:

// A lambda expression that takes two arguments and
// uses them to construct a temporary std::complex<>
construct< std::complex<int> >( _1, _2 )

Для обсуждения представьте, что у нас уже есть шаблон объекта функции<construct_impl<>>, который принимает аргументы и конструирует из них новые объекты. Мы хотели бы, чтобы приведенное выше выражение лямбда было эквивалентно следующему:

// The above lambda expression should be roughly equivalent
// to the following:
proto::make_expr<proto::tag::function>(
    construct_impl<std::complex<int> >() // The function to invoke lazily
  , boost::ref(_1)                       // The first argument to the function
  , boost::ref(_2)                       // The second argument to the function
);

Мы можем определить наш шаблон функции<construct()>следующим образом:

template<typename T, typename A0, typename A1>
typename proto::result_of::make_expr<
    proto::tag::function
  , construct_impl<T>
  , A0 const &
  , A1 const &
>::type const
construct(A0 const &a0, A1 const &a1)
{
    return proto::make_expr<proto::tag::function>(
        construct_impl<T>()
      , boost::ref(a0)
      , boost::ref(a1)
    );
}

Это работает для двух аргументов, но мы хотели бы, чтобы это работало для любого количества аргументов, вплоть до<BOOST_PROTO_MAX_ARITY>.- 1. Почему "- 1"? Потому что один ребенок занят терминалом<construct_impl<T>()>, оставляя место только для<BOOST_PROTO_MAX_ARITY>- 1] других детей.

Для таких случаев Proto предоставляет макросы<BOOST_PROTO_REPEAT>()и<BOOST_PROTO_REPEAT_FROM_TO>(). Чтобы использовать его, мы превращаем определение функции выше в макрос следующим образом:

#define M0(N, typename_A, A_const_ref, A_const_ref_a, ref_a)  \
template<typename T, typename_A(N)>                           \
typename proto::result_of::make_expr<                         \
    proto::tag::function                                      \
  , construct_impl<T>                                         \
  , A_const_ref(N)                                            \
>::type const                                                 \
construct(A_const_ref_a(N))                                   \
{                                                             \
    return proto::make_expr<proto::tag::function>(            \
        construct_impl<T>()                                   \
      , ref_a(N)                                              \
    );                                                        \
}

Обратите внимание, что мы превратили функцию в макрос, который принимает 5 аргументов. Первый — это текущий номер итерации. Остальные — названия других макросов, генерирующих разные последовательности. Например, Прото передает в качестве второго параметра имя макроса, которое будет расширяться до<typenameA0,typenameA1,...>.

Теперь, когда мы превратили нашу функцию в макрос, мы можем передать макрос<BOOST_PROTO_REPEAT_FROM_TO>(). Прото будет вызывать его итеративно, генерируя все перегрузки функции для нас.

// Generate overloads of construct() that accept from
// 1 to BOOST_PROTO_MAX_ARITY-1 arguments:
BOOST_PROTO_REPEAT_FROM_TO(1, BOOST_PROTO_MAX_ARITY, M0)
#undef M0
Non-Default Sequences

Как упоминалось выше, Proto передает в качестве последних 4 аргументов вашему макросу имена других макросов, которые генерируют различные последовательности. Макросы<BOOST_PROTO_REPEAT>()и<BOOST_PROTO_REPEAT_FROM_TO>()выбирают по умолчанию эти параметры. Если по умолчанию не удовлетворяются ваши потребности, вы можете использовать<BOOST_PROTO_REPEAT_EX>()и<BOOST_PROTO_REPEAT_FROM_TO_EX>()и передавать разные макросы, которые генерируют разные последовательности. Прото определяет ряд таких макросов для использования в качестве параметров к<BOOST_PROTO_REPEAT_EX>()и<BOOST_PROTO_REPEAT_FROM_TO_EX>(). Для всех подробностей ознакомьтесь с разделом<boost/proto/repeat.hpp>.

Смотрите также<BOOST_PROTO_LOCAL_ITERATE>. Он работает аналогично<BOOST_PROTO_REPEAT>и друзьям, но его легче использовать, когда вы хотите изменить один макро аргумент и принять по умолчанию для других.

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

The expr<> Type

Все выражения Прото являются инстанциацией шаблона под названием<proto::expr<>>(или оберткой вокруг такой инстанциации). Когда мы определяем терминал как ниже, мы действительно инициализируем экземпляр шаблона<proto::expr<>>.

// Define a placeholder type
template<int I>
struct placeholder
{};
// Define the Protofied placeholder terminal
proto::terminal< placeholder<0> >::type const _1 = {{}};

Тип<_1>выглядит следующим образом:

proto::expr< proto::tag::terminal, proto::term< placeholder<0> >, 0 >

Шаблон<proto::expr<>>является наиболее важным типом в Proto. Хотя вам редко придется иметь дело с ним напрямую, он всегда находится за кулисами, держа деревья выражения вместе. На самом деле<proto::expr<>>являетсядеревом выражения — ветви, листья и все.

Шаблон<proto::expr<>>образует узлы в деревьях выражения. Первым параметром шаблона является тип узла; в этом случае<proto::tag::terminal>. Это означает, что<_1>является лиственным узлом в дереве выражения. Второй параметр шаблона представляет собой список типов детей или, в случае терминалов, тип значений терминала. Терминалы всегда имеют только один тип в списке типов. Последний параметр — это ловкость выражения. Терминалы имеют arity 0, унарные выражения имеют arity 1, и т.д.

Структура<proto::expr<>>определяется следующим образом:

template< typename Tag, typename Args, long Arity = Args::arity >
struct expr;
template< typename Tag, typename Args >
struct expr< Tag, Args, 1 >
{
    typedef typename Args::child0 proto_child0;
    proto_child0 child0;
    // ...
};

Структура<proto::expr<>>не определяет конструктор или что-либо еще, что предотвратило бы статическую инициализацию. Все<proto::expr<>>объекты инициализируются с использованиемагрегированной инициализации, с кудрявыми брекетами. В нашем примере<_1>инициализируется инициализатором<{{}}>. Внешние брекеты являются инициализатором для структуры<proto::expr<>>, а внутренние брекеты - для элемента<_1.child0>, который имеет тип<placeholder<0>>. Обратите внимание, что мы используем брекеты для инициализации<_1.child0>, потому что<placeholder<0>>также является агрегатом.

Building Expression Trees

Узел<_1>является инстанциацией<proto::expr<>>, а выражения, содержащие<_1>, также являются инстанциациями<proto::expr<>>. Чтобы эффективно использовать Proto, вам не придется беспокоиться о реальных типах, которые генерирует Proto. Это детали, но вы, вероятно, столкнетесь с этими типами в сообщениях об ошибках компилятора, поэтому полезно ознакомиться с ними. Типы выглядят так:

// The type of the expression -_1
typedef
    proto::expr<
        proto::tag::negate
      , proto::list1<
            proto::expr<
                proto::tag::terminal
              , proto::term< placeholder<0> >
              , 0
            > const &
        >
      , 1
    >
negate_placeholder_type;
negate_placeholder_type x = -_1;
// The type of the expression _1 + 42
typedef
    proto::expr<
        proto::tag::plus
      , proto::list2<
            proto::expr<
                proto::tag::terminal
              , proto::term< placeholder<0> >
              , 0
            > const &
          , proto::expr<
                proto::tag::terminal
              , proto::term< int const & >
              , 0
            >
        >
      , 2
    >
placeholder_plus_int_type;
placeholder_plus_int_type y = _1 + 42;

Есть несколько вещей, чтобы отметить об этих типах:

  • Терминалы имеют arity zero, унарные выражения имеют arity one и бинарные выражения имеют arity two.
  • Когда одно выражение Прото делается детским узлом другого выражения Прото, оно удерживается ссылкой, даже если оно является временным объектом. Этот последний пункт становится важным позже.
  • Непрото-выражения, такие как целые буквальные числа, превращаются в прото-выражения, обертывая их в новые<expr<>>терминальные объекты. Эти новые обертки сами по себе не удерживаются ссылкой, но объект, обернутый, является. Обратите внимание, что тип Protofied<42>буквально<intconst &>- удерживается ссылкой.

Типы дают понять: все в дереве экспрессии Прото удерживается ссылкой. Это означает, что строительство дерева экспрессии исключительно дешево. Это вообще не предполагает копирования.

[Note]Note

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

Собрав выражение в дерево, вы, естественно, захотите сделать обратное и получить доступ к детям узла. Вы даже можете захотеть повторить над детьми с помощью алгоритмов от Boost. Библиотека Fusion. В этом разделе показано как.

Getting Expression Tags and Arities

Каждый узел в дереве экспрессии имеет как тип тега, который описывает узел, так иarity, соответствующий количеству дочерних узлов, которые он имеет. Вы можете использовать<proto::tag_of<>>и<proto::arity_of<>>метафункции для их извлечения. Рассмотрим следующее:

template<typename Expr>
void check_plus_node(Expr const &)
{
    // Assert that the tag type is proto::tag::plus
    BOOST_STATIC_ASSERT((
        boost::is_same<
            typename proto::tag_of<Expr>::type
          , proto::tag::plus
        >::value
    ));
    // Assert that the arity is 2
    BOOST_STATIC_ASSERT( proto::arity_of<Expr>::value == 2 );
}
// Create a binary plus node and use check_plus_node()
// to verify its tag type and arity:
check_plus_node( proto::lit(1) + 2 );

Для данного типа<Expr>вы можете получить доступ к тегу<Expr::proto_tag>и<Expr::proto_arity>, где<Expr::proto_arity>является интегральной константой MPL.

Getting Terminal Values

Нет более простого выражения, чем терминал, и нет более простой операции, чем извлечение его значения. Как мы уже видели, это то, для чего<proto::value()>.

proto::terminal< std::ostream & >::type cout_ = {std::cout};
// Get the value of the cout_ terminal:
std::ostream & sout = proto::value( cout_ );
// Assert that we got back what we put in:
assert( &sout == &std::cout );

Для вычисления типа возврата функции<proto::value()>можно использовать<proto::result_of::value<>>. Когда параметр<proto::result_of::value<>>является нессылочным типом, результирующий тип метафункции представляет собой тип значения, подходящего для хранения по значению; то есть от него лишаются эталона верхнего уровня и квалификаторов. Но при инстанцированном с эталонным типом тип результата имеет ссылку, добавленную к нему, что дает тип, подходящий для хранения посредством ссылки. Если вы хотите узнать фактический тип значения терминала, в том числе, хранится ли он по значению или ссылке, вы можете использовать<fusion::result_of::value_at<Expr,0>::type>.

В нижеследующей таблице резюмируется вышеупомянутый пункт.

Table 29.4. Accessing Value Types

Метафункция вызов

Если тип стоимости является...

Результатом является...

<proto::result_of::value<Expr>::type>

<T>

typename boost::remove_const<
    typename boost::remove_reference<T>::type
>::type [a]

<proto::result_of::value<Expr&>::type>

<T>

typename boost::add_reference<T>::type

<proto::result_of::value<Exprconst &>::type>

<T>

typename boost::add_reference<
    typename boost::add_const<T>::type
>::type

<fusion::result_of::value_at<Expr, 0>::type>

<T>

<T>

[a]Если<T>является типом отсылки к функции, то тип результата просто<T>.


Getting Child Expressions

Каждый нетерминальный узел в дереве экспрессии соответствует оператору в выражении, а дети соответствуют операндам или аргументам оператора. Для доступа к ним вы можете использовать шаблон функции<proto::child_c()>, как показано ниже:

proto::terminal<int>::type i = {42};
// Get the 0-th operand of an addition operation:
proto::terminal<int>::type &ri = proto::child_c<0>( i + 2 );
// Assert that we got back what we put in:
assert( &i == &ri );

Вы можете использовать метафункцию<proto::result_of::child_c<>>, чтобы получить тип N-го ребенка экспрессионного узла. Обычно вам все равно, хранится ли ребенок по значению или по ссылке, поэтому, когда вы спрашиваете тип N-го ребенка выражения<Expr>(где<Expr>не является эталонным типом), вы получаете тип ребенка после того, как ссылки и cv-квалификаторы были удалены из него.

template<typename Expr>
void test_result_of_child_c(Expr const &expr)
{
    typedef typename proto::result_of::child_c<Expr, 0>::type type;
    // Since Expr is not a reference type,
    // result_of::child_c<Expr, 0>::type is a
    // non-cv qualified, non-reference type:
    BOOST_MPL_ASSERT((
        boost::is_same< type, proto::terminal<int>::type >
    ));
}
// ...
proto::terminal<int>::type i = {42};
test_result_of_child_c( i + 2 );

Однако, если вы запрашиваете тип Nth ребенка<Expr &>или<Expr const&>(обратите внимание на ссылку), тип результата будет ссылкой, независимо от того, действительно ли ребенок хранится по ссылке или нет. Если вам нужно точно знать, как ребенок хранится в узле, будь то по ссылке или по стоимости, вы можете использовать<fusion::result_of::value_at<Expr,N>::type>. В следующей таблице описано поведение метафункции<proto::result_of::child_c<>>.

Table 29.5. Accessing Child Types

Метафункция вызов

Когда ребенок становится...

Результатом является...

<proto::result_of::child_c<Expr, N>::type>

<T>

typename boost::remove_const<
    typename boost::remove_reference<T>::type
>::type

<proto::result_of::child_c<Expr&, N>::type>

<T>

typename boost::add_reference<T>::type

<proto::result_of::child_c<Exprconst &,N>::type>

<T>

typename boost::add_reference<
    typename boost::add_const<T>::type
>::type

<fusion::result_of::value_at<Expr, N>::type>

<T>

<T>


Common Shortcuts

Большинство операторов на C++ являются унарными или двоичными, поэтому доступ к единственному операнду или левому и правому операндам является очень распространенным действием. По этой причине Proto предоставляет функции<proto::child()>,<proto::left()>и<proto::right()>.<proto::child()>и<proto::left()>являются синонимами<proto::child_c<0>()>, а<proto::right()>является синонимом<proto::child_c<1>()>.

Есть также<proto::result_of::child<>>,<proto::result_of::left<>>и<proto::result_of::right<>>метафункции, которые просто пересылаются своим<proto::result_of::child_c<>>аналогам.

Когда вы строите шаблон выражения с помощью Proto, все промежуточные узлы ребенка удерживаютсяпо ссылке. Избегает ненужных копий, что имеет решающее значение, если вы хотите, чтобы ваш EDSL хорошо работал во время выполнения. Естественно, существует опасность, если временные объекты выходят из-под контроля, прежде чем пытаться оценить шаблон выражения. Это особенно проблема в C++0x с новыми<decltype>и<auto>ключевыми словами. Подумайте:

// OOPS: "ex" is left holding dangling references
auto ex = proto::lit(1) + 2;

Проблема может возникнуть в современном C++, если вы используете<BOOST_TYPEOF()>или<BOOST_AUTO()>, или если вы пытаетесь передать шаблон выражения за пределы его составляющих.

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

// OK, "ex" has no dangling references
auto ex = proto::deep_copy( proto::lit(1) + 2 );

Если вы используетеBoost.Typeof, это будет выглядеть так:

// OK, use BOOST_AUTO() and proto::deep_copy() to
// store an expression template in a local variable 
BOOST_AUTO( ex, proto::deep_copy( proto::lit(1) + 2 ) );

Для работы вышеупомянутого кода необходимо включить заголовок<boost/proto/proto_typeof.hpp>, который также определяет макрос<BOOST_PROTO_AUTO>, который автоматически копирует его аргумент. С<BOOST_PROTO_AUTO>()вышеупомянутый код может быть записан как:

// OK, BOOST_PROTO_AUTO() automatically deep-copies
// its argument: 
BOOST_PROTO_AUTO( ex, proto::lit(1) + 2 );

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

[Note]Note

<proto::deep_copy()>не делает исключения для массивов, которые он хранит по стоимости. Это может привести к копированию большого количества данных.

Proto предоставляет утилиту для красивой печати деревьев экспрессии, которая очень удобна, когда вы пытаетесь отладить свой EDSL. Он называется<proto::display_expr()>, и вы передаете ему выражение для печати и необязательно<std::ostream>для отправки вывода. Подумайте:

// Use display_expr() to pretty-print an expression tree
proto::display_expr(
    proto::lit("hello") + 42
);

Приведенный выше код пишет об этом<std::cout>:

plus(
    terminal(hello)
  , terminal(42)
)

Для вызова<proto::display_expr()>все терминалы в выражении должны быть потоковыми (то есть они могут быть записаны на<std::ostream>). Кроме того, все типы тегов также должны быть потоковыми. Вот пример, который включает пользовательский тип терминала и пользовательский тег:

// A custom tag type that is Streamable
struct MyTag
{
    friend std::ostream &operator<<(std::ostream &s, MyTag)
    {
        return s << "MyTag";
    }
};
// Some other Streamable type
struct MyTerminal
{
    friend std::ostream &operator<<(std::ostream &s, MyTerminal)
    {
        return s << "MyTerminal";
    }
};
int main()
{
    // Display an expression tree that contains a custom
    // tag and a user-defined type in a terminal
    proto::display_expr(
        proto::make_expr<MyTag>(MyTerminal()) + 42
    );
}

Приведенный выше код печатает следующее:

plus(
    MyTag(
        terminal(MyTerminal)
    )
  , terminal(42)
)

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

Table 29.6. Operators, Tags and Metafunctions

оператор

Proto Tag

Прото-метафункция

unary<+>

<proto::tag::unary_plus>

proto::unary_plus<>

unary<->

<proto::tag::negate>

<proto::negate<>>

унарный<*>

<proto::tag::dereference>

<proto::dereference<>>

unary<~>

<proto::tag::complement>

<proto::complement<>>

unary<&>

<proto::tag::address_of>

<proto::address_of<>>

unary<!>

<proto::tag::logical_not>

<proto::logical_not<>>

унарный пристав<++>

<proto::tag::pre_inc>

<proto::pre_inc<>>

унарный пристав<-->

<proto::tag::pre_dec>

<proto::pre_dec<>>

unary postfix<++>

<proto::tag::post_inc>

<proto::post_inc<>>

unary postfix<-->

<proto::tag::post_dec>

<proto::post_dec<>>

двоичный<<<>

<proto::tag::shift_left>

<proto::shift_left<>>

двоичный<>>>

<proto::tag::shift_right>

<proto::shift_right<>>

двоичный<*>

<proto::tag::multiplies>

<proto::multiplies<>>

двоичный</>

<proto::tag::divides>

<proto::divides<>>

двоичный<%>

<proto::tag::modulus>

<proto::modulus<>>

двоичный<+>

<proto::tag::plus>

<proto::plus<>>

двоичный<->

<proto::tag::minus>

<proto::minus<>>

двоичный<<>

<proto::tag::less>

<proto::less<>>

двоичный<>>

<proto::tag::greater>

<proto::greater<>>

двоичный<<=>

<proto::tag::less_equal>

<proto::less_equal<>>

двоичный<>=>

<proto::tag::greater_equal>

<proto::greater_equal<>>

<proto::tag::equal_to>

<proto::equal_to<>>

двоичный<!=>

<proto::tag::not_equal_to>

<proto::not_equal_to<>>

двоичный<||>

<proto::tag::logical_or>

<proto::logical_or<>>

двоичный<&&>

<proto::tag::logical_and>

<proto::logical_and<>>

двоичный<&>

<proto::tag::bitwise_and>

<proto::bitwise_and<>>

двоичный<|>

<proto::tag::bitwise_or>

<proto::bitwise_or<>>

двоичный<^>

<proto::tag::bitwise_xor>

<proto::bitwise_xor<>>

двоичный<,>

<proto::tag::comma>

<proto::comma<>>

двоичный<->*>

<proto::tag::mem_ptr>

<proto::mem_ptr<>>

двоичный<=>

<proto::tag::assign>

<proto::assign<>>

двоичный<<<=>

<proto::tag::shift_left_assign>

<proto::shift_left_assign<>>

двоичный<>>=>

<proto::tag::shift_right_assign>

<proto::shift_right_assign<>>

двоичный<*=>

<proto::tag::multiplies_assign>

<proto::multiplies_assign<>>

двоичный</=>

<proto::tag::divides_assign>

<proto::divides_assign<>>

двоичный<%=>

<proto::tag::modulus_assign>

<proto::modulus_assign<>>

двоичный<+=>

<proto::tag::plus_assign>

<proto::plus_assign<>>

двоичный<-=>

<proto::tag::minus_assign>

<proto::minus_assign<>>

двоичный<&=>

<proto::tag::bitwise_and_assign>

<proto::bitwise_and_assign<>>

двоичный<|=>

<proto::tag::bitwise_or_assign>

<proto::bitwise_or_assign<>>

двоичный<^=>

<proto::tag::bitwise_xor_assign>

<proto::bitwise_xor_assign<>>

Бинарный подстрочный

<proto::tag::subscript>

<proto::subscript<>>

тройной<?:>

<proto::tag::if_else_>

<proto::if_else_<>>

вызов функции n-ary

<proto::tag::function>

<proto::function<>>


Повышаю. Fusion - это библиотека итераторов, алгоритмов, контейнеров и адаптеров для манипулирования гетерогенными последовательностями. По сути, выражение Прото является просто гетерогенной последовательностью его детских выражений, и поэтому выражения Прото являются действительными последовательностями случайного доступа Fusion. Это означает, что вы можете применять к ним алгоритмы Fusion, преобразовывать их, применять к ним фильтры Fusion и просмотры, а также получать доступ к их элементам с помощью<fusion::at()>. То, что Fusion может сделать с гетерогенными последовательностями, выходит за рамки руководства для пользователей, но ниже приведен простой пример. Он принимает вызов ленивой функции, как<fun(1,2,3,4)>и использует Fusion для печати аргументов функции в порядке.

struct display
{
    template<typename T>
    void operator()(T const &t) const
    {
        std::cout << t << std::endl;
    }
};
struct fun_t {};
proto::terminal<fun_t>::type const fun = {{}};
// ...
fusion::for_each(
    fusion::transform(
        // pop_front() removes the "fun" child
        fusion::pop_front(fun(1,2,3,4))
        // Extract the ints from the terminal nodes
      , proto::functional::value()
    )
  , display()
);

Вспомним из Введения, что типы в пространстве имен<proto::functional>определяют функциональные объекты, которые соответствуют свободным функциям Прото. Так<proto::functional::value()>создает объект функции, эквивалентный функции<proto::value()>. Приведенные выше ссылки<fusion::for_each()>показывают следующее:

1
2
3
4

Терминалы также являются действительными последовательностями Fusion. Они содержат один элемент: их ценность.

Flattening Proto Expression Tress

Представьте себе небольшую вариацию вышеприведенного примера, где вместо того, чтобы повторяться над аргументами ленивого вызова функции, мы хотели бы повторить над терминалами в дополнительном выражении:

proto::terminal<int>::type const _1 = {1};
// ERROR: this doesn't work! Why?
fusion::for_each(
    fusion::transform(
        _1 + 2 + 3 + 4
      , proto::functional::value()
    )
  , display()
);

Причина, по которой это не работает, заключается в том, что выражение<_1 +2+3+ 4>не описывает плоскую последовательность терминалов — оно описывает двоичное дерево. Однако мы можем рассматривать его как плоскую последовательность терминалов, используя функцию Прото<proto::flatten()>.<proto::flatten()>возвращает вид, который заставляет дерево выглядеть как плоская последовательность слияния. Если самый верхний узел имеет тип тега<T>, то элементами сплющенной последовательности являются дочерние узлы, которые имеют тип тега, а не<T>. Этот процесс оценивается рекурсивно. Таким образом, вышесказанное может быть правильно написано как:

proto::terminal<int>::type const _1 = {1};
// OK, iterate over a flattened view
fusion::for_each(
    fusion::transform(
        proto::flatten(_1 + 2 + 3 + 4)
      , proto::functional::value()
    )
  , display()
);

Приведенные выше ссылки<fusion::for_each()>показывают следующее:

1
2
3
4

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

Представьте, что ваш EDSL представляет собой миниатюрный объект ввода-вывода с операциями iostream, которые выполняются лениво. Вы можете захотеть, чтобы выражения, представляющие операции ввода, обрабатывались одной функцией, а операции вывода обрабатывались другой функцией. Как бы ты это сделал?

Ответ заключается в написании шаблонов (а.к.а,грамматики), которые соответствуют структуре входных и выходных выражений. Proto предоставляет утилиты для определения грамматики и шаблон<proto::matches<>>для проверки соответствия данного типа выражения грамматике.

Во-первых, давайте определим некоторые терминалы, которые мы можем использовать в наших ленивых выражениях ввода/вывода:

proto::terminal< std::istream & >::type cin_ = { std::cin };
proto::terminal< std::ostream & >::type cout_ = { std::cout };

Теперь мы можем использовать<cout_>вместо<std::cout>и получить деревья выражения ввода/вывода, которые мы можем выполнить позже. Для определения грамматики, соответствующей входным и выходным выражениям формы<cin_ >>i>и<cout_<< 1>, мы делаем следующее:

struct Input
  : proto::shift_right< proto::terminal< std::istream & >, proto::_ >
{};
struct Output
  : proto::shift_left< proto::terminal< std::ostream & >, proto::_ >
{};

Мы видели шаблон<proto::terminal<>>раньше, но здесь мы используем его без доступа к вложенному<::type>. При таком использовании это очень простая грамматика, как<proto::shift_right<>>и<proto::shift_left<>>. Новичок здесь<_>в пространстве имен<proto>. Это дикая карта, которая соответствует всему. Структура<Input>- это грамматика, которая соответствует любому выражению правого смещения, которое имеет терминал<std::istream>в качестве левого операнда.

Мы можем использовать эти грамматики вместе с шаблоном<proto::matches<>>для запроса во время компиляции, является ли данный тип выражения ввода/вывода операцией ввода или вывода. Рассмотрим следующее:

template< typename Expr >
void input_output( Expr const & expr )
{
    if( proto::matches< Expr, Input >::value )
    {
        std::cout << "Input!\n";
    }
    if( proto::matches< Expr, Output >::value )
    {
        std::cout << "Output!\n";
    }
}
int main()
{
    int i = 0;
    input_output( cout_ << 1 );
    input_output( cin_ >> i );
    return 0;
}

Эта программа печатает следующее:

Output!
Input!

Если мы хотим разбить функцию<input_output()>на две функции, одну, которая обрабатывает входные выражения и одну для выходных выражений, мы можем использовать<boost::enable_if<>>следующим образом:

template< typename Expr >
typename boost::enable_if< proto::matches< Expr, Input > >::type
input_output( Expr const & expr )
{
    std::cout << "Input!\n";
}
template< typename Expr >
typename boost::enable_if< proto::matches< Expr, Output > >::type
input_output( Expr const & expr )
{
    std::cout << "Output!\n";
}

Это работает так же, как и предыдущая версия. Однако нижеследующее вообще не составляет:

input_output( cout_ << 1 << 2 ); // oops!

Что случилось? Проблема в том, что это выражение не соответствует нашей грамматике. Выражение группы, как если бы оно было написано<(cout_<<1)<<2>. Он не будет соответствовать грамматике<Output>, которая ожидает, что левый операнд будет терминалом, а не другой операцией левой смены. Нам нужно исправить грамматику.

Мы замечаем, что для того, чтобы проверить выражение в качестве входа или выхода, нам нужно будет повторить до самого нижнего левого листа и проверить, что это<std::istream>или<std::ostream>. Когда мы доберемся до терминала, мы должны перестать повторяться. Мы можем выразить это в нашей грамматике, используя<proto::or_<>>. Вот правильные<Input>и<Output>грамматики:

struct Input
  : proto::or_<
        proto::shift_right< proto::terminal< std::istream & >, proto::_ >
      , proto::shift_right< Input, proto::_ >
    >
{};
struct Output
  : proto::or_<
        proto::shift_left< proto::terminal< std::ostream & >, proto::_ >
      , proto::shift_left< Output, proto::_ >
    >
{};

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

Соответствие выражения<cout_ <<1 <<2>грамматике<Output>происходит следующим образом:

  1. Первая альтернатива<proto::or_<>>пробуется первой. Оно не сработает, потому что выражение<cout_<< 1<< 2>не соответствует грамматике<proto::shift_left< proto::terminal< std::ostream& >,proto::_>>.
  2. Затем второй вариант пробуют следующим. Мы сопоставляем выражение против<proto::shift_left< Output, proto::_>>. Выражение - левая смена, поэтому мы попытаемся сопоставить операнды.
  3. Правый операнд<2>соответствует<proto::_>тривиально.
  4. Чтобы увидеть, соответствует ли левый операнд<cout_ <<1><Output>, мы должны рекурсивно оценить грамматику<Output>. На этот раз мы преуспеем, потому что<cout_ <<1>будет соответствовать первой альтернативе<proto::or_<>>.

Мы закончили - грамматика успешно совпадает.

Терминалы в дереве экспрессии могут быть ссылками на const или non-const, или они могут вообще не быть ссылками. При написании грамматики вам обычно не нужно беспокоиться об этом, потому что<proto::matches<>>дает вам немного места для маневра при сопоставлении терминалов. Грамматика, такая как<proto::terminal<int>>, будет соответствовать терминалу типа<int>,<int&>или<intconst &>.

Вы можете четко указать, что вы хотите соответствовать типу ссылки. Если да, то тип должен точно совпадать. Например, грамматика, такая как<proto::terminal<int&>>, будет соответствовать только<int&>. Он не будет соответствовать<int>или<int const&>.

В таблице ниже показано, как Proto соответствует терминалам. Простое правило: если вы хотите соответствовать только типам ссылок, вы должны указать ссылку в своей грамматике. В противном случае оставьте это, и Proto будет игнорировать конст и ссылки.

Table 29.7. proto::matches<> and Reference / CV-Qualification of Terminals

Терминал

грамматика

Спички?

Т

Т

Да

T &

Т

Да

T const &

Т

Да

Т

T &

Нет

T &

T &

Да

T const &

T &

Нет

Т

T const &

Нет

T &

T const &

Нет

T const &

T const &

Да


Напрашивается вопрос: Что делать, если вы хотите соответствовать<int>, но не<int&>или<intconst &>? Для форсирования точных совпадений Proto предоставляет шаблон<proto::exact<>>. Например,<proto::terminal<proto::exact<int>>>соответствует<int>, удерживаемому по стоимости.

Proto дает вам дополнительное пространство для маневра при сопоставлении типов массивов. Типы массивов соответствуют самим себе или типам указателей, на которые они распадаются. Это особенно полезно для массивов символов. Возвращенный<proto::as_expr("hello")>тип<proto::terminal<charconst[6]>::type>. Это терминал, содержащий 6-элементный массив символов. Естественно, вы можете сопоставить этот терминал с грамматикой<proto::terminal<charconst[6]>>, но грамматика<proto::terminal<charconst*>>также будет соответствовать ему, как иллюстрирует следующий фрагмент кода.

struct CharString
  : proto::terminal< char const * >
{};
typedef proto::terminal< char const[6] >::type char_array;
BOOST_MPL_ASSERT(( proto::matches< char_array, CharString > ));

Что, если бы мы только хотели, чтобы<CharString>соответствовал терминалам именно того типа<char const*>? Вы можете использовать<proto::exact<>>здесь, чтобы отключить нечеткое соответствие терминалов следующим образом:

struct CharString
  : proto::terminal< proto::exact< char const * > >
{};
typedef proto::terminal<char const[6]>::type char_array;
typedef proto::terminal<char const *>::type  char_string;
BOOST_MPL_ASSERT(( proto::matches< char_string, CharString > ));
BOOST_MPL_ASSERT_NOT(( proto::matches< char_array, CharString > ));

Теперь<CharString>не соответствует типам массивов, а только указателям строк символов.

Обратная проблема немного сложнее: что, если вы хотите сопоставить все наборы символов, но не указатели персонажей? Как уже упоминалось выше, выражение<as_expr("hello")>имеет тип<proto::terminal<charconst[6]>::type>. Если вы хотите сопоставить массивы символов произвольного размера, вы можете использовать<proto::N>, который представляет собой дикую карту размером с массив. Следующая грамматика будет соответствовать любой строке буквально:<proto::terminal<charconst[proto::N]>>.

Иногда вам нужно еще больше места для маневра при сопоставлении терминалов. Например, возможно, вы строите калькулятор EDSL и хотите разрешить любые терминалы, которые можно конвертировать в<double>. Для этого Proto предоставляет шаблон<proto::convertible_to<>>. Вы можете использовать его как:<proto::terminal<proto::convertible_to<double >>>.

Есть еще один способ выполнить нечеткий матч на терминалах. Рассмотрим проблему сопоставления терминала<std::complex<>>. Вы можете легко сопоставить<std::complex<float>>или<std::complex<double>>, но как бы вы сопоставили любую инстанцию<std::complex<>>? Вы можете использовать<proto::_>здесь, чтобы решить эту проблему. Вот грамматика, чтобы соответствовать любой<std::complex<>>инстанциации:

struct StdComplex
  : proto::terminal< std::complex< proto::_ > >
{};

Когда дается такая грамматика, Прото деконструирует грамматику и терминал, с которым она сопоставляется, и посмотрит, может ли она соответствовать всем составляющим.

Мы уже видели, как использовать генераторы выражений<proto::terminal<>>и<proto::shift_right<>>в качестве грамматики. Мы также видели<proto::or_<>>, который мы можем использовать для выражения набора альтернативных грамматик. Есть несколько других, представляющих интерес; в частности,<proto::if_<>>,<proto::and_<>>и<proto::not_<>>.

Шаблон<proto::not_<>>самый простой. Он принимает грамматику как параметр шаблона и логически отрицает ее;<not_<Grammar>>будет соответствовать любому выражению, которое<Grammar>делает, а не.

Шаблон<proto::if_<>>используется вместе с преобразованием Прото, которое оценивается по типам экспрессии для поиска совпадений. (Прототрансформация будет описана позже.)

Шаблон<proto::and_<>>подобен<proto::or_<>>, за исключением того, что каждый аргумент<proto::and_<>>должен соответствовать<proto::and_<>>. В качестве примера рассмотрим определение<CharString>выше, которое использует<proto::exact<>>. Он мог быть написан без<proto::exact<>>следующим образом:

struct CharString
  : proto::and_<
        proto::terminal< proto::_ >
      , proto::if_< boost::is_same< proto::_value, char const * >() >
    >
{};

Это говорит о том, что<CharString>должен быть терминалом,иего тип значения должен быть таким же, как<charconst *>. Обратите внимание на шаблонный аргумент<proto::if_<>>:<boost::is_same<proto::_value,charconst*>()>. Это преобразование Прото, которое сравнивает тип значения терминала с<char const*>.

Шаблон<proto::if_<>>имеет несколько вариантов. В дополнение к<if_<Condition>>можно также сказать<if_<Condition,ThenGrammar>>и<if_<Condition,ThenGrammar,ElseGrammar>>. Они позволяют выбрать одну подграмму или другую на основе<Condition>.

Когда ваша грамматика Proto станет большой, вы начнете сталкиваться с некоторыми проблемами масштабируемости с<proto::or_<>>, конструкцией, которую вы используете для указания альтернативных подграмм. Во-первых, из-за ограничений в C++<proto::or_<>>может принимать только определенное количество подграмм, контролируемых макросом<BOOST_PROTO_MAX_LOGICAL_ARITY>. Этот макрос по умолчанию равен восьми, и вы можете установить его выше, но это усугубит еще одну проблему масштабируемости: длительное время компиляции. С<proto::or_<>>альтернативные подграммы проверяются в порядке, как серия каскадных<if>, что приводит к множеству ненужных шаблонных инстанциаций. Вместо этого вы предпочли бы что-то вроде<switch>, что позволяет избежать затрат на каскадирование<if>'s. Это цель<proto::switch_<>>; хотя это и менее удобно, чем<proto::or_<>>, это улучшает время компиляции для более крупных грамматик и не имеет произвольного фиксированного ограничения на количество подграмм.

Давайте проиллюстрируем, как использовать<proto::switch_<>>, сначала написав большую грамматику<proto::or_<>>, а затем переведя ее на эквивалентную грамматику, используя<proto::switch_<>>:

// Here is a big, inefficient grammar
struct ABigGrammar
  : proto::or_<
        proto::terminal<int>
      , proto::terminal<double>
      , proto::unary_plus<ABigGrammar>
      , proto::negate<ABigGrammar>
      , proto::complement<ABigGrammar>
      , proto::plus<ABigGrammar, ABigGrammar>
      , proto::minus<ABigGrammar, ABigGrammar>
      , proto::or_<
            proto::multiplies<ABigGrammar, ABigGrammar>
          , proto::divides<ABigGrammar, ABigGrammar>
          , proto::modulus<ABigGrammar, ABigGrammar>
        >
    >
{};

Выше может быть грамматика для более сложного калькулятора EDSL. Заметьте, что, поскольку существует более восьми подграмм, мы должны были связать подграммы с вложенным<proto::or_<>>.

Идея, лежащая в основе<proto::switch_<>>, заключается в отправке на основе типа тега выражения в подграмму, которая обрабатывает выражения этого типа. Для использования<proto::switch_<>>вы определяете структуру с вложенным<case_<>>шаблоном, специализирующимся на типах тегов. Вышеприведенная грамматика может быть выражена следующим образом<proto::switch_<>>. Это описано ниже.

// Redefine ABigGrammar more efficiently using proto::switch_<>
struct ABigGrammar;
struct ABigGrammarCases
{
    // The primary template matches nothing:
    template<typename Tag>
    struct case_
      : proto::not_<_>
    {};
};
// Terminal expressions are handled here
template<>
struct ABigGrammarCases::case_<proto::tag::terminal>
  : proto::or_<
        proto::terminal<int>
      , proto::terminal<double>
    >
{};
// Non-terminals are handled similarly
template<>
struct ABigGrammarCases::case_<proto::tag::unary_plus>
  : proto::unary_plus<ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::negate>
  : proto::negate<ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::complement>
  : proto::complement<ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::plus>
  : proto::plus<ABigGrammar, ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::minus>
  : proto::minus<ABigGrammar, ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::multiplies>
  : proto::multiplies<ABigGrammar, ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::divides>
  : proto::divides<ABigGrammar, ABigGrammar>
{};
template<>
struct ABigGrammarCases::case_<proto::tag::modulus>
  : proto::modulus<ABigGrammar, ABigGrammar>
{};
// Define ABigGrammar in terms of ABigGrammarCases
// using proto::switch_<>
struct ABigGrammar
  : proto::switch_<ABigGrammarCases>
{};

Сопоставление типа выражения<E>с<proto::switch_<C>>эквивалентно сопоставлению его с<C::case_<E::proto_tag>>. Отправляя по типу тега выражения, мы можем перейти к подграмме, которая обрабатывает выражения этого типа, пропуская все другие подграммы, которые не могут соответствовать. Если нет специализации<case_<>>для конкретного типа тега, мы выбираем основной шаблон. В этом случае основной шаблон наследуется от<proto::not_<_>>, который не соответствует выражениям.

Обратите внимание на специализацию, которая обрабатывает терминалы:

// Terminal expressions are handled here
template<>
struct ABigGrammarCases::case_<proto::tag::terminal>
  : proto::or_<
        proto::terminal<int>
      , proto::terminal<double>
    >
{};

Тип<proto::tag::terminal>сам по себе не достаточно, чтобы выбрать соответствующую подграмму, поэтому мы используем<proto::or_<>>, чтобы перечислить альтернативные подграммы, которые соответствуют терминалам.

[Note]Note

У вас может возникнуть соблазн определить свои специализации<case_<>>in situследующим образом:

struct ABigGrammarCases
{
    template<typename Tag>
    struct case_ : proto::not_<_> {};
    // ERROR: not legal C++
    template<>
    struct case_<proto::tag::terminal>
      /* ... */
};

К сожалению, по тайным причинам незаконным является определение явной вложенной специализацииin situ. Однако совершенно законно определятьчастичныеспециализацииin situ, поэтому вы можете добавить дополнительный параметр шаблона шаблона, который имеет по умолчанию, следующим образом:

struct ABigGrammarCases
{
    // Note extra "Dummy" template parameter here:
    template<typename Tag, int Dummy = 0>
    struct case_ : proto::not_<_> {};
    // OK: "Dummy" makes this a partial specialization
    // instead of an explicit specialization.
    template<int Dummy>
    struct case_<proto::tag::terminal, Dummy>
      /* ... */
};

Вы можете найти это более чистым, чем определение явных специализаций<case_<>>за пределами их структуры.

Не все перегружаемые операторы C++ являются унарными или бинарными. Существует нечетное число<operator()>— оператор вызова функции — которое может иметь любое количество аргументов. Аналогичным образом, с Proto вы можете определить своих собственных «операторов», которые также могут принять более двух аргументов. В результате в дереве экспрессии Прото могут быть узлы, имеющие произвольное число детей (до<BOOST_PROTO_MAX_ARITY>, которое настраивается). Как написать грамматику, чтобы соответствовать такому узлу?

Для таких случаев Proto предоставляет шаблон класса<proto::vararg<>>. Его аргумент шаблона является грамматикой, и 1408 будет соответствовать грамматике ноль или более раз. Рассмотрим ленивую функцию Proto, называемую<fun()>, которая может принимать в качестве аргументов ноль или более символов:

struct fun_tag {};
struct FunTag : proto::terminal< fun_tag > {};
FunTag::type const fun = {{}};
// example usage:
fun();
fun('a');
fun('a', 'b');
...

Ниже приведена грамматика, которая соответствует всем допустимым призывам<fun()>:

struct FunCall
  : proto::function< FunTag, proto::vararg< proto::terminal< char > > >
{};

Грамматика<FunCall>использует<proto::vararg<>>для соответствия нулю или более буквальным символам в качестве аргументов функции<fun()>.

В качестве другого примера, можете ли вы угадать, что соответствует следующей грамматике?

struct Foo
  : proto::or_<
        proto::terminal< proto::_ >
      , proto::nary_expr< proto::_, proto::vararg< Foo > >
    >
{};

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

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

[Tip]Tip

Вы можете подумать, что это обратный способ делать вещи.& #8220;Если бы Proto позволил мне выбрать, какие операторы перегружать, мои пользователи не смогли бы создать недействительные выражения в первую очередь, и мне бы вообще не нужна была грамматика!& #8221;Это может быть правдой, но есть причины, по которым люди предпочитают делать что-то именно так.

Во-первых, это позволяет вам быстро развивать свой EDSL - все операторы уже есть для вас! - и беспокоиться о недействительном синтаксисе позже.

Во-вторых, некоторые операторы могут быть разрешены только в определенных контекстах. Это легко выразить с помощью грамматики, и трудно сделать с прямой перегрузкой оператора.

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

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

Если ничто из вышеперечисленного не убеждает вас, вы на самом делеможетеиспользовать Proto для контроля того, какие операторы перегружены в вашем домене. И для этого нужно определить грамматику!

В предыдущем разделе мы использовали Proto для определения EDSL для лениво оцененного калькулятора, который позволял любую комбинацию заполнителей, буквы с плавающей точкой, сложение, вычитание, умножение, деление и группирование. Если бы мы написали грамматику для этого EDSL вEBNF, это могло бы выглядеть так:

group       ::= '(' expression ')'
factor      ::= double | '_1' | '_2' | group
term        ::= factor (('*' factor) | ('/' factor))*
expression  ::= term (('+' term) | ('-' term))*

Это фиксирует синтаксис, ассоциативность и правила приоритета калькулятора. Написание грамматики для нашего калькулятора EDSL с помощью Protoеще проще. Поскольку мы используем C++ в качестве основного языка, мы привязаны к правилам ассоциативности и приоритетности для операторов C++. Наша грамматика может их принять. Кроме того, в C++ группирование уже обрабатывается для нас с использованием скобки, поэтому нам не нужно кодировать это в нашей грамматике.

Давайте начнем нашу грамматику, чтобы объявить ее вперед:

struct CalculatorGrammar;

На данный момент это неполный тип, но мы все равно сможем использовать его для определения правил нашей грамматики. Определим правила грамматики для терминалов:

struct Double
  : proto::terminal< proto::convertible_to< double > >
{};
struct Placeholder1
  : proto::terminal< placeholder<0> >
{};
struct Placeholder2
  : proto::terminal< placeholder<1> >
{};
struct Terminal
  : proto::or_< Double, Placeholder1, Placeholder2 >
{};

Теперь определим правила сложения, вычитания, умножения и деления. Здесь мы можем игнорировать вопросы ассоциативности и приоритетности — компилятор C++ будет обеспечивать это для нас. Мы должны лишь настаивать на том, чтобы доводы операторов сами соответствовали тому<CalculatorGrammar>, о котором мы заявляли выше.

struct Plus
  : proto::plus< CalculatorGrammar, CalculatorGrammar >
{};
struct Minus
  : proto::minus< CalculatorGrammar, CalculatorGrammar >
{};
struct Multiplies
  : proto::multiplies< CalculatorGrammar, CalculatorGrammar >
{};
struct Divides
  : proto::divides< CalculatorGrammar, CalculatorGrammar >
{};

Теперь, когда мы определили все части грамматики, мы можем определить<CalculatorGrammar>:

struct CalculatorGrammar
  : proto::or_<
        Terminal
      , Plus
      , Minus
      , Multiplies
      , Divides
    >
{};

Вот так! Теперь мы можем использовать<CalculatorGrammar>для обеспечения того, чтобы шаблон выражения соответствовал нашей грамматике. Мы можем использовать<proto::matches<>>и<BOOST_MPL_ASSERT()>для выпуска читаемых ошибок компиляции для недействительных выражений, как показано ниже:

template< typename Expr >
void evaluate( Expr const & expr )
{
    BOOST_MPL_ASSERT(( proto::matches< Expr, CalculatorGrammar > ));
    // ...
}

Теперь, когда вы написали переднюю часть для компилятора EDSL и немного узнали о промежуточной форме, которую он производит, пришло время подумать о том, что делатьс промежуточной формой. Здесь вы размещаете свои алгоритмы и оптимизации для домена. Proto дает вам два способа оценки и управления шаблонами выражения: контексты и преобразования.

  • Контекстподобен объекту функции, который вы передаете вместе с выражением функции<proto::eval()>. Он связывает поведение с типами узлов.<proto::eval()>ходит по выражению и вызывает ваш контекст в каждом узле.
  • Преобразование— это способ ассоциировать поведение не с типами узлов в выражении, а с правилами в грамматике Прото. Таким образом, они похожи на семантические действия в других инструментах построения компиляторов.

Два способа оценки выражений! Как выбрать? Поскольку контексты в основном процедурные, их немного проще понять и отладить, поэтому они являются хорошим местом для начала. Но хотя преобразования более продвинуты, они также более мощны; поскольку они связаны с правилами в вашей грамматике, вы можете выбрать правильное преобразование на основе всей структурыподвыражения, а не просто по типу его верхнего узла.

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

После того, как вы построили дерево экспрессии Прото, либо с помощью перегрузок оператора Прото, либо с помощью<proto::make_expr()>и друзей, вы, вероятно, захотите на самом делесделатьчто-то с ним. Самый простой вариант — использовать<proto::eval()>, общий оценщик выражений. Чтобы использовать<proto::eval()>, вам нужно определитьконтекст, который сообщает<proto::eval()>, как каждый узел должен быть оценен. В этом разделе рассматриваются гайки и болты использования<proto::eval()>, определение контекстов оценки и использование контекстов, которые предоставляет Proto.

[Note]Note

<proto::eval()>является менее мощным, но более простым в использовании методом оценки, чем трансформация Прото, которая охватывается позже. Хотя они очень мощные, преобразования имеют крутую кривую обучения и могут быть более сложными для отладки.<proto::eval()>— довольно слабый алгоритм обхода деревьев. Дэн Марсден работает над более общей и мощной библиотекой по обходу деревьев. Когда он будет готов, я ожидаю, что он устранит необходимость<proto::eval()>.

Синопсис:

namespace proto
{
    namespace result_of
    {
        // A metafunction for calculating the return
        // type of proto::eval() given certain Expr
        // and Context types.
        template<typename Expr, typename Context>
        struct eval
        {
            typedef
                typename Context::template eval<Expr>::result_type
            type;
        };
    }
    namespace functional
    {
        // A callable function object type for evaluating
        // a Proto expression with a certain context.
        struct eval : callable
        {
            template<typename Sig>
            struct result;
            template<typename Expr, typename Context>
            typename proto::result_of::eval<Expr, Context>::type
            operator ()(Expr &expr, Context &context) const;
            template<typename Expr, typename Context>
            typename proto::result_of::eval<Expr, Context>::type
            operator ()(Expr &expr, Context const &context) const;
        };
    }
    template<typename Expr, typename Context>
    typename proto::result_of::eval<Expr, Context>::type
    eval(Expr &expr, Context &context);
    template<typename Expr, typename Context>
    typename proto::result_of::eval<Expr, Context>::type
    eval(Expr &expr, Context const &context);
}

Учитывая выражение и контекст оценки, использование<proto::eval()>довольно просто. Просто передайте выражение и контекст<proto::eval()>, и он сделает все остальное и вернет результат. Вы можете использовать метафункцию<eval<>>в пространстве имен<proto::result_of>для вычисления типа возврата<proto::eval()>. Ниже показано использование<proto::eval()>:

template<typename Expr>
typename proto::result_of::eval<Expr const, MyContext>::type
MyEvaluate(Expr const &expr)
{
    // Some user-defined context type
    MyContext ctx;
    // Evaluate an expression with the context
    return proto::eval(expr, ctx);
}

То, что делает<proto::eval()>, также очень просто. Он относит большую часть работы к самому контексту. Вот, по существу, реализация<proto::eval()>:

// eval() dispatches to a nested "eval<>" function
// object within the Context:
template<typename Expr, typename Context>
typename Context::template eval<Expr>::result_type
eval(Expr &expr, Context &ctx)
{
    typename Context::template eval<Expr> eval_fun;
    return eval_fun(expr, ctx);
}

Действительно,<proto::eval()>— это не что иное, как тонкая обертка, которая посылает соответствующему обработчику в рамках контекстного класса. В следующем разделе мы увидим, как реализовать класс контекста с нуля.

Как мы видели в предыдущем разделе, в функции<proto::eval()>действительно не так много. Скорее, вся интересная оценка выражения проходит в рамках контекстного класса. В этом разделе показано, как реализовать один с нуля.

Все классы контекста имеют примерно следующую форму:

// A prototypical user-defined context.
struct MyContext
{
    // A nested eval<> class template
    template<
        typename Expr
      , typename Tag = typename proto::tag_of<Expr>::type
    >
    struct eval;
    // Handle terminal nodes here...
    template<typename Expr>
    struct eval<Expr, proto::tag::terminal>
    {
        // Must have a nested result_type typedef.
        typedef ... result_type;
        // Must have a function call operator that takes
        // an expression and the context.
        result_type operator()(Expr &expr, MyContext &ctx) const
        {
            return ...;
        }
    };
    // ... other specializations of struct eval<> ...
};

Контекстные классы — это не что иное, как набор специализаций вложенного шаблона классов<eval<>>. Каждая специализация имеет различный тип выражения.

В разделеHello Calculatorмы увидели пример определяемого пользователем класса контекста для оценки выражений калькулятора. Этот класс контекста был реализован с помощью Прото<proto::callable_context<>>. Если бы мы реализовали его с нуля, это выглядело бы примерно так:

// The calculator_context from the "Hello Calculator" section,
// implemented from scratch.
struct calculator_context
{
    // The values with which we'll replace the placeholders
    std::vector<double> args;
    template<
        typename Expr
        // defaulted template parameters, so we can
        // specialize on the expressions that need
        // special handling.
      , typename Tag = typename proto::tag_of<Expr>::type
      , typename Arg0 = typename proto::child_c<Expr, 0>::type
    >
    struct eval;
    // Handle placeholder terminals here...
    template<typename Expr, int I>
    struct eval<Expr, proto::tag::terminal, placeholder<I> >
    {
        typedef double result_type;
        result_type operator()(Expr &, MyContext &ctx) const
        {
            return ctx.args[I];
        }
    };
    // Handle other terminals here...
    template<typename Expr, typename Arg0>
    struct eval<Expr, proto::tag::terminal, Arg0>
    {
        typedef double result_type;
        result_type operator()(Expr &expr, MyContext &) const
        {
            return proto::child(expr);
        }
    };
    // Handle addition here...
    template<typename Expr, typename Arg0>
    struct eval<Expr, proto::tag::plus, Arg0>
    {
        typedef double result_type;
        result_type operator()(Expr &expr, MyContext &ctx) const
        {
            return proto::eval(proto::left(expr), ctx)
                 + proto::eval(proto::right(expr), ctx);
        }
    };
    // ... other eval<> specializations for other node types ...
};

Теперь мы можем использовать<proto::eval()>с классом контекста выше для оценки выражений калькулятора следующим образом:

// Evaluate an expression with a calculator_context
calculator_context ctx;
ctx.args.push_back(5);
ctx.args.push_back(6);
double d = proto::eval(_1 + _2, ctx);
assert(11 == d);

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

Proto предоставляет некоторые готовые классы контекста, которые вы можете использовать как есть, или которые вы можете использовать, чтобы помочь при реализации ваших собственных контекстов. Они:

default_context

Контекст оценки, который присваивает обычные значения C++ всем операторам. Например, дополнительные узлы обрабатываются путем оценки левых и правых детей, а затем добавляют результаты. В 1498 г. используется буст. Тип для определения типов выражений, которые он оценивает.

null_context

Простой контекст, который рекурсивно оценивает детей, но никак не комбинирует результаты и возвращает пустоту.

callable_context<>

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

<proto::default_context>— это контекст оценки, который присваивает всем операторам обычные значения C++. Например, дополнительные узлы обрабатываются путем оценки левых и правых детей, а затем добавляют результаты.<proto::default_context>использует Boost. Тип для определения типов выражений, которые он оценивает.

Рассмотрим, например, следующий пример «Hello World»:

#include <iostream>
#include <boost/proto/proto.hpp>
#include <boost/proto/context.hpp>
#include <boost/typeof/std/ostream.hpp>
using namespace boost;
proto::terminal< std::ostream & >::type cout_ = { std::cout };
template< typename Expr >
void evaluate( Expr const & expr )
{
    // Evaluate the expression with default_context,
    // to give the operators their C++ meanings:
    proto::default_context ctx;
    proto::eval(expr, ctx);
}
int main()
{
    evaluate( cout_ << "hello" << ',' << " world" );
    return 0;
}

Эта программа предусматривает следующее:

hello, world

<proto::default_context>тривиально определяется в терминах шаблона<default_eval<>>следующим образом:

// Definition of default_context
struct default_context
{
    template<typename Expr>
    struct eval
      : default_eval<
            Expr
          , default_context const
          , typename tag_of<Expr>::type
        >
    {};
};

Существует множество специализаций<default_eval<>>, каждая из которых обрабатывает другого оператора C++. Вот, например, специализация для бинарного сложения:

// A default expression evaluator for binary addition
template<typename Expr, typename Context>
struct default_eval<Expr, Context, proto::tag::plus>
{
private:
    static Expr    & s_expr;
    static Context & s_ctx;
public:
    typedef
        decltype(
            proto::eval(proto::child_c<0>(s_expr), s_ctx)
          + proto::eval(proto::child_c<1>(s_expr), s_ctx)
        )
    result_type;
    result_type operator ()(Expr &expr, Context &ctx) const
    {
        return proto::eval(proto::child_c<0>(expr), ctx)
             + proto::eval(proto::child_c<1>(expr), ctx);
    }
};

Приведенный выше код использует<decltype>для вычисления типа возврата оператора вызова функции.<decltype>— новое ключевое слово в следующей версии C++, которое получает тип любого выражения. Большинство компиляторов пока не поддерживают<decltype>напрямую, поэтому<default_eval<>>использует Boost. Тип библиотеки для подражания. На некоторых компиляторах это может означать, что<default_context>либо не работает, либо требует, чтобы вы регистрировали свои типы с помощью Boost. Тип библиотеки. Проверьте документацию для Boost. Тип, чтобы увидеть.

1510 — это простой контекст, который рекурсивно оценивает детей, но никоим образом не комбинирует результаты и возвращает пустоту. Это полезно в сочетании с<callable_context<>>или при определении ваших собственных контекстов, которые мутируют дерево выражения на месте, а не накапливают результат, как мы увидим ниже.

<proto::null_context<>>тривиально реализуется в терминах<null_eval<>>следующим образом:

// Definition of null_context
struct null_context
{
    template<typename Expr>
    struct eval
      : null_eval<Expr, null_context const, Expr::proto_arity::value>
    {};
};

И<null_eval<>>также тривиально реализован. Вот, например, двоичный<null_eval<>>:

// Binary null_eval<>
template<typename Expr, typename Context>
struct null_eval<Expr, Context, 2>
{
    typedef void result_type;
    void operator()(Expr &expr, Context &ctx) const
    {
        proto::eval(proto::child_c<0>(expr), ctx);
        proto::eval(proto::child_c<1>(expr), ctx);
    }
};

Когда такие классы будут полезны? Представьте, что у вас есть дерево выражений с целыми терминалами, и вы хотите увеличить каждое целое число на месте. Вы можете определить контекст оценки следующим образом:

struct increment_ints
{
    // By default, just evaluate all children by delegating
    // to the null_eval<>
    template<typename Expr, typename Arg = proto::result_of::child<Expr>::type>
    struct eval
      : null_eval<Expr, increment_ints const>
    {};
    // Increment integer terminals
    template<typename Expr>
    struct eval<Expr, int>
    {
        typedef void result_type;
        void operator()(Expr &expr, increment_ints const &) const
        {
            ++proto::child(expr);
        }
    };
};

В следующем разделе<proto::callable_context<>>мы увидим еще более простой способ добиться того же.

<proto::callable_context<>>— помощник, упрощающий работу по написанию контекстных классов. Вместо написания шаблонных специализаций с 1517 вы пишете объект функции с перегруженным оператором вызова функции. Любые выражения, не обработанные перегрузкой, автоматически отправляются в контекст оценки по умолчанию, который вы можете указать.

Вместо контекста оценки в его собственном праве<proto::callable_context<>>более правильно мыслится как адаптер контекста. Чтобы использовать его, вы должны определить свой собственный контекст, который наследуется от<proto::callable_context<>>.

В разделе<null_context>мы увидели, как реализовать контекст оценки, который увеличивает все целые числа в дереве экспрессии. Вот как сделать то же самое с<proto::callable_context<>>:

// An evaluation context that increments all
// integer terminals in-place.
struct increment_ints
  : callable_context<
        increment_ints const // derived context
      , null_context const  // fall-back context
    >
{
    typedef void result_type;
    // Handle int terminals here:
    void operator()(proto::tag::terminal, int &i) const
    {
        ++i;
    }
};

С таким контекстом мы можем сделать следующее:

literal<int> i = 0, j = 10;
proto::eval( i - j * 3.14, increment_ints() );
std::cout << "i = " << i.get() << std::endl;
std::cout << "j = " << j.get() << std::endl;

Эта программа выводит следующее, которое показывает, что целые числа<i>и<j>были увеличены<1>:

i = 1
j = 11

В контексте<increment_ints>нам не нужно было определять какие-либо вложенные шаблоны<eval<>>. Это потому, что<proto::callable_context<>>реализует их для нас.<proto::callable_context<>>берет два параметра шаблона: производный контекст и обратный контекст. Для каждого узла в оцениваемом дереве экспрессии<proto::callable_context<>>проверяет, есть ли перегруженный<operator()>в производном контексте, который принимает его. Учитывая некоторое выражение<expr>типа<Expr>и контекст<ctx>, он пытается назвать:

ctx(
    typename Expr::proto_tag()
  , proto::child_c<0>(expr)
  , proto::child_c<1>(expr)
    ...
);

Используя трюки перегрузки функций и метапрограммирования,<proto::callable_context<>>может определить, существует ли такая функция или нет. Если да, то эта функция называется. Если нет, то текущее выражение передается в контекст обратной оценки для обработки.

Мы видели еще один пример<proto::callable_context<>>, когда смотрели на простой калькулятор выражения оценщик. Там мы хотели настроить оценку терминалов-заполнителей и делегировать обработку всех других узлов<proto::default_context>. Мы сделали это следующим образом:

// An evaluation context for calculator expressions that
// explicitly handles placeholder terminals, but defers the
// processing of all other nodes to the default_context.
struct calculator_context
  : proto::callable_context< calculator_context const >
{
    std::vector<double> args;
    // Define the result type of the calculator.
    typedef double result_type;
    // Handle the placeholders:
    template<int I>
    double operator()(proto::tag::terminal, placeholder<I>) const
    {
        return this->args[I];
    }
};

В этом случае мы не указали обратный контекст. В этом случае<proto::callable_context<>>использует<proto::default_context>. С приведенными выше<calculator_context>и несколькими соответствующим образом определенными терминалами заполнителя мы можем оценить выражения калькулятора, как показано ниже:

template<int I>
struct placeholder
{};
terminal<placeholder<0> >::type const _1 = {{}};
terminal<placeholder<1> >::type const _2 = {{}};
// ...
calculator_context ctx;
ctx.args.push_back(4);
ctx.args.push_back(5);
double j = proto::eval( (_2 - _1) / _2 * 100, ctx );
std::cout << "j = " << j << std::endl;

Приведенный выше код отображает следующее:

j = 20

Если вы когда-либо строили парсер с помощью такого инструмента, как Antlr, yacc или Boost. Дух, вы можете быть знакомы ссемантических действий. В дополнение к определению грамматики языка, распознаваемого парсером, эти инструменты позволяют встраивать код в грамматику, которая выполняется, когда части грамматики участвуют в разборе. Прото имеет эквивалент семантических действий. Они называютсяпреобразованиями. В этом разделе описывается, как встраивать преобразования в грамматику Proto, превращая грамматику в функциональные объекты, которые могут манипулировать или оценивать выражения мощными способами.

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

Грамматика Прото, которую мы видели до сих пор, статична. Вы можете проверить, соответствует ли тип выражения грамматике, но это все. Вещи становятся более интересными, когда вы даете им поведение во время выполнения. Грамматика со встроенными преобразованиями — это больше, чем просто статическая грамматика. Это функциональный объект, который принимает выражения, соответствующие грамматике и делает.что-тос ними.

Ниже приведена очень простая грамматика. Он соответствует терминальным выражениям.

// A simple Proto grammar that matches all terminals
proto::terminal< _ >

Вот та же грамматика с преобразованием, которое извлекает значение из терминала:

// A simple Proto grammar that matches all terminals
// *and* a function object that extracts the value from
// the terminal
proto::when<
    proto::terminal< _ >
  , proto::_value          // <-- Look, a transform!
>

Вы можете прочитать это следующим образом: когда вы соответствуете терминальному выражению, извлеките значение. Тип<proto::_value>— так называемое преобразование. Позже мы увидим, что делает его трансформацией, но пока просто представим его как своего рода функциональный объект. Обратите внимание на использование<proto::when<>>: Первый параметр шаблона - это грамматика для соответствия, а второй - преобразование для выполнения. Результатом является как грамматика, которая соответствует терминальным выражениям, так и объект функции, который принимает терминальные выражения и извлекает их значения.

Как и в обычных грамматиках, мы можем определить пустую структуру, которая наследуется от грамматики + преобразования, чтобы дать нам простой способ вернуться к тому, что мы определяем, следующим образом:

// A grammar and a function object, as before
struct Value
  : proto::when<
        proto::terminal< _ >
      , proto::_value
    >
{};
// "Value" is a grammar that matches terminal expressions
BOOST_MPL_ASSERT(( proto::matches< proto::terminal<int>::type, Value > ));
// "Value" also defines a function object that accepts terminals
// and extracts their value.
proto::terminal<int>::type answer = {42};
Value get_value;
int i = get_value( answer );

Как уже упоминалось,<Value>является грамматикой, которая соответствует терминальным выражениям и функциональному объекту, который работает на терминальных выражениях. Было бы ошибкой передавать нетерминальное выражение объекту функции<Value>. Это общее свойство грамматики с преобразованиями; при использовании их в качестве функциональных объектов передаваемые им выражения должны соответствовать грамматике.

Протограмматики являются действительными объектами функций в стиле TR1. Это означает, что вы можете использовать<boost::result_of<>>, чтобы спросить грамматику, каким будет ее тип возврата, учитывая конкретный тип выражения. Например, мы можем получить доступ к типу возврата грамматики<Value>следующим образом:

// We can use boost::result_of<> to get the return type
// of a Proto grammar.
typedef
    typename boost::result_of<Value(proto::terminal<int>::type)>::type
result_type;
// Check that we got the type we expected
BOOST_MPL_ASSERT(( boost::is_same<result_type, int> ));
[Note]Note

Грамматика со встроенными преобразованиями является одновременно грамматикой и функциональным объектом. Называть эти вещи «грамматиками с преобразованиями» было бы утомительно. Мы могли бы назвать их чем-то вроде «активных грамматик», но, как мы увидим, каждаяграмматика, которую вы можете определить с помощью Прото, является «активной»; то есть каждая грамматика имеет некоторое поведение при использовании в качестве функционального объекта. Поэтому мы будем продолжать называть эти вещи простыми «грамматиками». Термин «трансформация» зарезервирован для того, что используется в качестве второго параметра шаблона<proto::when<>>.

Большинство грамматик немного сложнее, чем в предыдущем разделе. Для иллюстрации давайте определим довольно бессмысленную грамматику, которая соответствует любому выражению и повторяется до самого левого конца и возвращает его значение. Он продемонстрирует, как две ключевые концепции грамматики Прото — чередование и рекурсия — взаимодействуют с преобразованиями. Грамматика описана ниже.

// A grammar that matches any expression, and a function object
// that returns the value of the leftmost terminal.
struct LeftmostLeaf
  : proto::or_<
        // If the expression is a terminal, return its value
        proto::when<
            proto::terminal< _ >
          , proto::_value
        >
        // Otherwise, it is a non-terminal. Return the result
        // of invoking LeftmostLeaf on the 0th (leftmost) child.
      , proto::when<
            _
          , LeftmostLeaf( proto::_child0 )
        >
    >
{};
// A Proto terminal wrapping std::cout
proto::terminal< std::ostream & >::type cout_ = { std::cout };
// Create an expression and use LeftmostLeaf to extract the
// value of the leftmost terminal, which will be std::cout.
std::ostream & sout = LeftmostLeaf()( cout_ << "the answer: " << 42 << '\n' );

Мы уже видели<proto::or_<>>. Здесь он выполняет две роли. Во-первых, это грамматика, которая соответствует любой из его альтернативных подграмм; в этом случае, либо терминал или нетерминал. Во-вторых, это также функциональный объект, который принимает выражение, находит альтернативную подграмму, которая соответствует выражению, и применяет его преобразование. А поскольку<LeftmostLeaf>наследует от<proto::or_<>>,<LeftmostLeaf>является одновременно и грамматикой, и функциональным объектом.

[Note]Note

Второй вариант использует<proto::_>в качестве грамматики. Напомним, что<proto::_>— это грамматика wildcard, которая соответствует любому выражению. Так как в<proto::or_<>>чередования испробованы по порядку, и так как первый альтернативный обрабатывает все терминалы, второй альтернативный обрабатывает все (и только) нетерминалы. Достаточно часто<proto::when< _, some-transform>>является последней альтернативой в грамматике, поэтому для улучшения читаемости можно использовать эквивалент<proto::otherwise<some-transform>>.

Следующий раздел описывает эту грамматику далее.

В грамматике, определенной в предыдущем разделе, преобразование, связанное с нетерминалами, выглядит немного странно:

proto::when<
    _
  , LeftmostLeaf( proto::_child0 )   // <-- a "callable" transform
>

Он имеет эффект принятия не терминальных выражений, взятия 0-го (самого левого) ребенка и рекурсивного вызова на него функции<LeftmostLeaf>. Но<LeftmostLeaf(proto::_child0 )>на самом деле является.тип функции. Буквально это тип функции, которая принимает объект типа<proto::_child0>и возвращает объект типа<LeftmostLeaf>. Итак, как мы понимаем смысл этой трансформации? Очевидно, что нет никакой функции, которая действительно имеет эту подпись, и такая функция не была бы полезной. Ключ в понимании того, как<proto::when<>>интерпретируетего второй параметр шаблона.

Когда второй параметр шаблона<proto::when<>>является типом функции,<proto::when<>>интерпретирует тип функции как преобразование. В этом случае<LeftmostLeaf>рассматривается как тип объекта функции, на который следует ссылаться, а<proto::_child0>рассматривается как преобразование. Во-первых,<proto::_child0>применяется к текущему выражению (нетерминал, который соответствовал этой альтернативной подграмме), и результат (0-й ребенок) передается в качестве аргумента<LeftmostLeaf>.

[Note]Note

Преобразования — это специфический язык домена

<LeftmostLeaf( proto::_child0)>выглядиткак вызов объекта функции<LeftmostLeaf>, но это не так, но тогда это действительно так! Почему эта запутанная уловка? Типы функций дают нам естественный и краткий синтаксис для составления более сложных преобразований из более простых. Тот факт, что синтаксис предполагает вызов функции, является целенаправленным. Это встроенный доменный язык для определения преобразований выражения. Если уловка сработала, она, возможно, обманула вас, заставив думать, что трансформация делает именно то, что она на самом деле делает! И в этом весь смысл.

Тип<LeftmostLeaf(proto::_child0 )>является примеромвызывающего преобразования. Это тип функции, который представляет объект функции для вызова и его аргументы. Типы<proto::_child0>и<proto::_value>являютсяпримитивными преобразованиями. Это простые структуры, не похожие на функциональные объекты, из которых могут быть составлены вызывающие преобразования. Существует еще один тип преобразования,объект преобразует, с которым мы столкнемся далее.

Первое преобразование, которое мы рассмотрели, просто извлекло значение терминалов. Давайте сделаем то же самое, но на этот раз мы сначала продвигаем все инты в лонгс. (Пожалуйста, простите выдуманность примеров до сих пор; они становятся более интересными позже.) Вот грамматика:

// A simple Proto grammar that matches all terminals,
// and a function object that extracts the value from
// the terminal, promoting ints to longs:
struct ValueWithPomote
  : proto::or_<
        proto::when<
            proto::terminal< int >
          , long(proto::_value)     // <-- an "object" transform
        >
      , proto::when<
            proto::terminal< _ >
          , proto::_value
        >
    >
{};

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

[Note]Note

Object Transforms vs. Callable Transforms

При использовании типов функций в качестве преобразований Proto они могут либо представлять объект для построения, либо функцию для вызова. Это похоже на «нормальный» C++, где синтаксис<foo("arg")>может быть интерпретирован как объект для построения или функция для вызова, в зависимости от того, является ли<foo>типом или функцией. Но рассмотрим два преобразования, которые мы видели до сих пор:

LeftmostLeaf(proto::_child0)  // <-- a callable transform
long(proto::_value)           // <-- an object transform

Прото вообще не может знать, что есть что, поэтому для дифференциации он использует черту<proto::is_callable<>>.<is_callable<long >::value>является ложным, поэтому<long(proto::_value)>является объектом для построения, но<is_callable<LeftmostLeaf >::value>является истинным, поэтому<LeftmostLeaf(proto::_child0)>является функцией для вызова. Позже мы увидим, как Proto распознает тип как «призывной».

Теперь, когда у нас есть основы преобразования Proto, давайте рассмотрим более реалистичный пример. Мы можем использовать преобразования для повышения безопасности типа калькулятора EDSL. Если вы помните, он позволяет записать арифметические выражения, включающие заполнители аргументов, такие как<_1>и<_2>, и передать их алгоритмам STL в качестве функциональных объектов следующим образом:

double a1[4] = { 56, 84, 37, 69 };
double a2[4] = { 65, 120, 60, 70 };
double a3[4] = { 0 };
// Use std::transform() and a calculator expression
// to calculate percentages given two input sequences:
std::transform(a1, a1+4, a2, a3, (_2 - _1) / _2 * 100);

Это работает потому, что мы дали выражению калькулятора<operator()>, который оценивает выражение, заменяя заполнителей аргументами<operator()>. Перегруженный<calculator<>::operator()>выглядел так:

// Overload operator() to invoke proto::eval() with
// our calculator_context.
template<typename Expr>
double
calculator<Expr>::operator()(double a1 = 0, double a2 = 0) const
{
    calculator_context ctx;
    ctx.args.push_back(a1);
    ctx.args.push_back(a2);
    return proto::eval(*this, ctx);
}

Хотя это работает, это не идеально, потому что это не предупреждает пользователей, если они предоставляют слишком много или слишком мало аргументов для выражения калькулятора. Рассмотрим следующие ошибки:

(_1 * _1)(4, 2);  // Oops, too many arguments!
(_2 * _2)(42);    // Oops, too few arguments!

Выражение<_1* _1>определяет выражение унарного калькулятора; оно принимает один аргумент и квадратует его. Если мы примем более одного аргумента, дополнительные аргументы будут молча игнорироваться, что может удивить пользователей. Следующее выражение<_2 *_2>определяет выражение двоичного калькулятора; оно принимает два аргумента, игнорирует первый и квадратует второй. Если мы пропустим только один аргумент, код безмолвно заполнит<0.0>для второго аргумента, что также, вероятно, не соответствует ожиданиям пользователей. Что можно сделать?

Можно сказать, чтоаритмиявыражения калькулятора — это число аргументов, которое он ожидает, и оно равно наибольшему положению в выражении. Так, благодать<_1 *_1>одна, а благодать<_2 *_2>— две. Мы можем повысить безопасность типа нашего калькулятора EDSL, убедившись, что частота выражения равна фактическому количеству представленных аргументов. Вычислить аритмию выражения просто с помощью трансформаций Прото.

Легко описать словами, как должна быть рассчитана ловкость выражения. Рассмотрим, что выражения калькулятора могут быть сделаны из<_1>,<_2>, буквальных, унарных выражений и двоичных выражений. В следующей таблице показаны условия для каждого из этих 5 компонентов.

Table 29.8. Calculator Sub-Expression Arities

Подвыражение

Арти

Владелец 1

<1>

Владелец 2

<2>

Буквально

<0>

Унарное выражение

подвижность операнда

Бинарное выражение

Максимальная подвижность двух операндов


Используя эту информацию, мы можем написать грамматику для выражений калькулятора и прикрепить преобразования для вычисления частоты каждого компонента. Приведенный ниже код вычисляет выражение arity как целое число времени компиляции, используя интегральные обертки и метафункции из библиотеки Boost MPL. Грамматика описана ниже.

struct CalcArity
  : proto::or_<
        proto::when< proto::terminal< placeholder<0> >,
            mpl::int_<1>()
        >
      , proto::when< proto::terminal< placeholder<1> >,
            mpl::int_<2>()
        >
      , proto::when< proto::terminal<_>,
            mpl::int_<0>()
        >
      , proto::when< proto::unary_expr<_, CalcArity>,
            CalcArity(proto::_child)
        >
      , proto::when< proto::binary_expr<_, CalcArity, CalcArity>,
            mpl::max<CalcArity(proto::_left),
                     CalcArity(proto::_right)>()
        >
    >
{};

Когда мы находим терминал заполнителя или буквальный терминал, мы используем преобразование объекта, такое как<mpl::int_<1>()>, для создания целого числа времени компиляции (по умолчанию), представляющего собой удобство этого терминала.

Для унарных выражений мы используем<CalcArity(proto::_child)>, который представляет собойвызывающее вызов преобразование, вычисляющее порядочность ребенка выражения.

Трансформация для бинарных выражений имеет несколько новых приемов. Давайте посмотрим более внимательно:

// Compute the left and right arities and
// take the larger of the two.
mpl::max<CalcArity(proto::_left),
         CalcArity(proto::_right)>()

Это преобразование объекта; оно по умолчанию конструирует... что именно? Шаблон<mpl::max<>>представляет собой метафункцию MPL, которая принимает два целых числа времени компиляции. Он имеет вложенный<::type>типдеф (не показан), который является максимальным из двух. Но здесь мы, по-видимому, передаем ему две вещи, которые являются, а нецелыми числами времени компиляции; они являются трансформируемыми Прото. Прото достаточно умен, чтобы признать этот факт. Сначала он оценивает две вложенные переменные, вычисляя особенности выражений правого и левого ребенка. Затем он помещает полученные целые числа в<mpl::max<>>и оценивает метафункцию, запрашивая вложенное<::type>. Это тип объекта, который строится по умолчанию и возвращается.

В более общем плане, оценивая преобразование объекта, Proto смотрит на тип объекта и проверяет, является ли он шаблонной специализацией, как<mpl::max<>>. Если это так, Proto ищет вложенные преобразования, которые он может оценить. После того, как любые вложенные преобразования были оценены и заменены обратно в шаблон, новая специализация шаблона является типом результата, если только этот тип не имеет вложенного<::type>, и в этом случае это становится результатом.

Теперь, когда мы можем рассчитать скорость выражения калькулятора, давайте переопределим обертку выражения<calculator<>>, которую мы написали в руководстве «Начало работы», чтобы использовать грамматику<CalcArity>и некоторые макросы из Boost. MPL выдает ошибки компиляции, когда пользователи указывают слишком много или слишком мало аргументов.

// The calculator expression wrapper, as defined in the Hello
// Calculator example in the Getting Started guide. It behaves
// just like the expression it wraps, but with extra operator()
// member functions that evaluate the expression.
//   NEW: Use the CalcArity grammar to ensure that the correct
//   number of arguments are supplied.
template<typename Expr>
struct calculator
  : proto::extends<Expr, calculator<Expr>, calculator_domain>
{
    typedef
        proto::extends<Expr, calculator<Expr>, calculator_domain>
    base_type;
    calculator(Expr const &expr = Expr())
      : base_type(expr)
    {}
    typedef double result_type;
    // Use CalcArity to compute the arity of Expr: 
    static int const arity = boost::result_of<CalcArity(Expr)>::type::value;
    double operator()() const
    {
        BOOST_MPL_ASSERT_RELATION(0, ==, arity);
        calculator_context ctx;
        return proto::eval(*this, ctx);
    }
    double operator()(double a1) const
    {
        BOOST_MPL_ASSERT_RELATION(1, ==, arity);
        calculator_context ctx;
        ctx.args.push_back(a1);
        return proto::eval(*this, ctx);
    }
    double operator()(double a1, double a2) const
    {
        BOOST_MPL_ASSERT_RELATION(2, ==, arity);
        calculator_context ctx;
        ctx.args.push_back(a1);
        ctx.args.push_back(a2);
        return proto::eval(*this, ctx);
    }
};

Обратите внимание на использование<boost::result_of<>>для доступа к типу возврата объекта функции<CalcArity>. Поскольку мы использовали целые числа времени компиляции в наших трансформациях, частота выражения кодируется в обратном типе объекта функции<CalcArity>. Протограмматики являются действительными объектами функций в стиле TR1, поэтому вы можете использовать<boost::result_of<>>для определения их типов возврата.

С нашими утверждениями о времени компиляции, когда пользователи предоставляют слишком много или слишком мало аргументов для выражения калькулятора, например:

(_2 * _2)(42); // Oops, too few arguments!

Они получат сообщение об ошибке компиляции времени на строке с утверждением, что читает что-то подобное.<boost::proto>:

c:\boost\org\trunk\libs\proto\scratch\main.cpp(97) : error C2664: 'boost::mpl::asse
rtion_failed' : cannot convert parameter 1 from 'boost::mpl::failed ************boo
st::mpl::assert_relation<x,y,__formal>::************' to 'boost::mpl::assert<false>
::type'
   with
   [
       x=1,
       y=2,
       __formal=bool boost::mpl::operator==(boost::mpl::failed,boost::mpl::failed)
   ]

Цель этого упражнения состояла в том, чтобы показать, что мы можем написать довольно простую грамматику Прото со встроенными преобразованиями, которая является декларативной и читаемой и может вычислять интересные свойства произвольно сложных выражений. Но преобразования могут сделать больше, чем это. Повышаю. Xpressive использует преобразования для превращения выражений в конечные автоматы состояний для сопоставления регулярных выражений и Boost. Spirit использует преобразования для создания рекурсивных генераторов парсера спуска. Proto поставляется с набором встроенных преобразований, которые вы можете использовать для выполнения очень сложных манипуляций с выражением. В следующих разделах мы увидим некоторые из них в действии.

До сих пор мы видели только примеры грамматики с преобразованиями, которые принимают один аргумент: выражение для преобразования. Но подумайте на мгновение, как в обычном процедурном коде вы превратите двоичное дерево в связанный список. Начнем с пустого списка. Затем вы рекурсивно преобразуете правую ветвь в список и используете результат в качестве начального состояния при преобразовании левой ветви в список. То есть вам понадобится функция, которая принимает два параметра: текущий узел и список до сих пор. Такие проблемы накоплениядовольно распространены при обработке деревьев. Связанный список является примером переменной накопления илисостояния. Каждая итерация алгоритма принимает текущий элемент и состояние, применяет некоторую двоичную функцию к двум и создает новое состояние. В STL этот алгоритм называется<std::accumulate()>. Во многих других языках он называетсясгиб. Давайте посмотрим, как реализовать алгоритм сгибания с помощью преобразований Proto.

Все грамматики Proto могут необязательно принимать параметр состояния в дополнение к выражению для преобразования. Если вы хотите сложить дерево в список, вам нужно будет использовать параметр состояния, чтобы обойти список, который вы создали до сих пор. Что касается списка, то рост. Библиотека Fusion предоставляет тип<fusion::cons<>>, из которого можно строить гетерогенные списки. Тип<fusion::nil>представляет собой пустой список.

Ниже приведена грамматика, которая распознает выходные выражения, такие как<cout_<< 42<< '\n'>, и помещает аргументы в список Fusion. Это объясняется ниже.

// Fold the terminals in output statements like
// "cout_ << 42 << '\n'" into a Fusion cons-list.
struct FoldToList
  : proto::or_<
        // Don't add the ostream terminal to the list
        proto::when<
            proto::terminal< std::ostream & >
          , proto::_state
        >
        // Put all other terminals at the head of the
        // list that we're building in the "state" parameter
      , proto::when<
            proto::terminal<_>
          , fusion::cons<proto::_value, proto::_state>(
                proto::_value, proto::_state
            )
        >
        // For left-shift operations, first fold the right
        // child to a list using the current state. Use
        // the result as the state parameter when folding
        // the left child to a list.
      , proto::when<
            proto::shift_left<FoldToList, FoldToList>
          , FoldToList(
                proto::_left
              , FoldToList(proto::_right, proto::_state)
            )
        >
    >
{};

Прежде чем читать дальше, посмотрите, сможете ли вы применить то, что вы уже знаете об объектных, вызывающих и примитивных преобразованиях, чтобы выяснить, как работает эта грамматика.

Когда вы используете функцию<FoldToList>, вам нужно будет передать два аргумента: выражение для складывания и начальное состояние: пустой список. Эти два аргумента передаются каждой трансформации. Ранее мы узнали, что<proto::_value>является примитивным преобразованием, которое принимает терминальное выражение и извлекает его значение. До сих пор мы не знали, что она также принимает текущее состояниеи игнорирует его.<proto::_state>также является примитивным преобразованием. Она принимает текущее выражение, которое игнорирует, и текущее состояние, которое она возвращает.

Когда мы находим терминал, мы держим его во главе списка минусов, используя текущее состояние в качестве хвоста списка. (Первая альтернатива приводит к пропуску<ostream>). Мы не хотим<cout>в списке. Когда мы находим левый сдвиговый узел, мы применяем следующее преобразование:

// Fold the right child and use the result as
// state while folding the right.
FoldToList(
    proto::_left
  , FoldToList(proto::_right, proto::_state)
)

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

[Tip]Tip

Если компилятором является Microsoft Visual C++, вы обнаружите, что приведенное выше преобразование не компилируется. Компилятор имеет ошибки с его обработкой вложенных типов функций. Вы можете обойти ошибку, обернув внутреннее преобразование в<proto::call<>>следующим образом:

FoldToList(
    proto::_left
  , proto::call<FoldToList(proto::_right, proto::_state)>
)

<proto::call<>>превращает вызывающую трансформацию в примитивную, но об этом позже.

Теперь, когда мы определили объект функции<FoldToList>, мы можем использовать его для превращения выходных выражений в списки следующим образом:

proto::terminal<std::ostream &>::type const cout_ = {std::cout};
// This is the type of the list we build below
typedef
    fusion::cons<
        int
      , fusion::cons<
            double
          , fusion::cons<
                char
              , fusion::nil
            >
        >
    >
result_type;
// Fold an output expression into a Fusion list, using
// fusion::nil as the initial state of the transformation.
FoldToList to_list;
result_type args = to_list(cout_ << 1 << 3.14 << '\n', fusion::nil());
// Now "args" is the list: {1, 3.14, '\n'}

При написании преобразований «складка» является такой базовой операцией, что Proto обеспечивает ряд встроенных сгибаемых преобразований. Мы доберемся до них позже. На данный момент будьте уверены, что вам не всегда придется растягивать свой мозг, чтобы делать такие простые вещи.

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

Давайте изменим наш предыдущий пример так, чтобы он записывал каждый терминал в<std::cout>, прежде чем поместить его в список. Это может быть удобно для отладки ваших преобразований, например. Мы можем сделать его общим, передав<std::ostream>в преобразование в параметре данных. В самом преобразовании мы можем восстановить<ostream>с преобразованием<proto::_data>. Стратегия такова: используйте преобразование<proto::and_<>>, чтобы зацепить два действия. Второе действие создаст узел<fusion::cons<>>, как и раньше. Первое действие, однако, будет отображать текущее выражение. Для этого мы сначала создаем экземпляр<proto::functional::display_expr>, а затем называем его.

// Fold the terminals in output statements like
// "cout_ << 42 << '\n'" into a Fusion cons-list.
struct FoldToList
  : proto::or_<
        // Don't add the ostream terminal to the list
        proto::when<
            proto::terminal< std::ostream & >
          , proto::_state
        >
        // Put all other terminals at the head of the
        // list that we're building in the "state" parameter
      , proto::when<
            proto::terminal<_>
          , proto::and_<
                // First, write the terminal to an ostream passed
                // in the data parameter
                proto::lazy<
                    proto::make<proto::functional::display_expr(proto::_data)>(_)
                >
                // Then, constuct the new cons list.
              , fusion::cons<proto::_value, proto::_state>(
                    proto::_value, proto::_state
                )
            >
        >
        // For left-shift operations, first fold the right
        // child to a list using the current state. Use
        // the result as the state parameter when folding
        // the left child to a list.
      , proto::when<
            proto::shift_left<FoldToList, FoldToList>
          , FoldToList(
                proto::_left
              , FoldToList(proto::_right, proto::_state, proto::_data)
              , proto::_data
            )
        >
    >
{};

Это многое, без сомнения. Но обратите внимание на второй пункт<when>выше. Когда вы найдете терминал, сначала отобразите терминал, используя<ostream>, который вы найдете в параметре данных, затем возьмите значение терминала и текущее состояние, чтобы построить новый список<cons>. Функциональный объект<display_expr>выполняет работу по печати терминала, а<proto::and_<>>связывает действия вместе и выполняет их последовательно, возвращая результат последнего.

[Note]Note

Новый<proto::lazy<>>. Иногда у вас нет готового вызывающего объекта для выполнения. Вместо этого вы хотите сначала сделать один изатемвыполнить его. Выше мы должны создать<display_expr>, инициализировав его с нашим<ostream>. После этого мы хотим вызвать его, передав ему текущее выражение. Это как если бы мы делали<display_expr(std::cout)(the-expr)>. Мы получаем эту двухфазную оценку, используя<proto::lazy<>>. Если это еще не имеет смысла, не беспокойтесь об этом.

Мы можем использовать приведенное выше преобразование, как и раньше, но теперь мы можем пройти<ostream>в качестве третьего параметра и наблюдать преобразование в действии. Вот пример использования:

proto::terminal<std::ostream &>::type const cout_ = {std::cout};
// This is the type of the list we build below
typedef
    fusion::cons<
        int
      , fusion::cons<
            double
          , fusion::cons<
                char
              , fusion::nil
            >
        >
    >
result_type;
// Fold an output expression into a Fusion list, using
// fusion::nil as the initial state of the transformation.
// Pass std::cout as the data parameter so that we can track
// the progress of the transform on the console.
FoldToList to_list;
result_type args = to_list(cout_ << 1 << 3.14 << '\n', fusion::nil(), std::cout);
// Now "args" is the list: {1, 3.14, '\n'}

Этот код отображает следующее:

terminal(
)
terminal(3.14)
terminal(1)

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

Transform Environment Variables
[Note]Note

Это продвинутая тема. Не стесняйтесь пропустить, если вы новичок в Proto.

В приведенном выше примере параметр данных используется в качестве транспортного механизма для неструктурированного набора данных; в этом случае ссылка на<ostream>. По мере того, как ваши алгоритмы Proto становятся все более сложными, вы можете обнаружить, что с неструктурированным набором данных не очень удобно работать. Различные части вашего алгоритма могут быть заинтересованы в различных битах данных. Вместо этого вам нужен способ передачи в наборепеременных средыпреобразованию, подобно набору пар ключ/значение. Затем вы можете легко получить желаемую часть данных, запросив параметр данных для значения, связанного с конкретным ключом. Прототрансформирует окружающую среду.

Начнем с определения ключа.

BOOST_PROTO_DEFINE_ENV_VAR(mykey_type, mykey);

Это определяет глобальную постоянную<mykey>с типом<mykey_type>. Мы можем использовать<mykey>для хранения части ассоциированных данных в среде преобразования, так как:

// Call the MyEval algorithm with a transform environment containing
// two key/value pairs: one for proto::data and one for mykey
MyEval()( expr, state, (proto::data = 42, mykey = "hello world") );

Вышеуказанное означает вызов алгоритма<MyEval>с тремя параметрами: выражением, начальным состоянием и средой преобразования, содержащей две пары ключ/значение.

В рамках алгоритма Proto вы можете получить доступ к значениям, связанным с различными ключами, используя преобразование<proto::_env_var<>>. Например,<proto::_env_var<mykey_type>>получил бы значение<"hello world">из среды преобразования, созданной выше.

Трансформация<proto::_data>имеет несколько дополнительных умов. Вместо того, чтобы всегда возвращать третий параметр независимо от того, является ли он каплей или средой преобразования, он сначала проверяет, является ли он каплей или нет. Если это так, то это то, что возвращается. Если нет, он возвращает значение, связанное с ключом<proto::data>. В приведенном выше примере это будет значение<42>.

Существует небольшое количество функций, метафункций и классов, которые вы можете использовать для создания и манипулирования средами преобразования, некоторые для проверки того, является ли объект средой преобразования, некоторые для принуждения объекта быть средой преобразования, а некоторые для запроса среды преобразования, имеет ли значение для конкретного ключа. Для исчерпывающего рассмотрения темы ознакомьтесь со ссылкой на заголовок<boost/proto/transform/env.hpp>.

Давайте воспользуемся<FoldToList>примером из предыдущих двух разделов, чтобы проиллюстрировать некоторые другие тонкости преобразований Прото. Мы видели, что грамматика при использовании в качестве функциональных объектов может принимать до 3 параметров, и что при использовании этих грамматик в вызывающих преобразованиях также можно указать до 3 параметров. Давайте еще раз взглянем на трансформацию, связанную с нетерминалами из последнего раздела:

FoldToList(
    proto::_left
  , FoldToList(proto::_right, proto::_state, proto::_data)
  , proto::_data
)

Здесь мы указываем все три параметра для обоих вызовов грамматики<FoldToList>. Но нам не нужно указывать все три. Если мы не укажем третий параметр,<proto::_data>предполагается. Аналогично для второго параметра и<proto::_state>. Таким образом, приведенное выше преобразование могло быть написано более просто:

FoldToList(
    proto::_left
  , StringCopy(proto::_right)
)

То же самое относится и к любой примитивной трансформации. Все они эквивалентны:

Table 29.9. Implicit Parameters to Primitive Transforms

Эквивалентные преобразования

<proto::when<_, FoldToList>>

<proto::when<_, FoldToList()>>

<proto::when<_, FoldToList(_)>>

<proto::when<_, FoldToList(_, proto::_state)>>

<proto::when<_, FoldToList(_, proto::_state, proto::_data)>>


[Note]Note

Грамматики — первичные преобразования — функциональные объекты

До сих пор мы говорили, что все грамматики Прото являются функциональными объектами. Но точнее сказать, что грамматика Прото — это примитивные преобразования — особый вид функционального объекта, который принимает от 1 до 3 аргументов, и который Прото знает, как обращаться с ним, особенно когда он используется в вызывающем преобразовании.

[Note]Note

Не все функциональные объекты являются первичными преобразованиями

Теперь у вас может возникнуть соблазн отказаться от параметров<_state>и<_data>для всех ваших перемен. Это было бы ошибкой. Вы можете сделать это только для примитивных трансформаций, и не все вызываемые являются примитивными трансформациями. Позже мы увидим, что отличает обычных кабельных от их более мощных примитивных кузенов-трансфоров, но короткая версия такова: примитивные трансформы наследуют от<proto::transform<>>.

Как только вы знаете, что примитивные преобразования всегда будут получать все три параметра — выражение, состояние и данные — это делает возможным то, что не было бы иначе. Например, для двоичных выражений эти два преобразования эквивалентны. Ты видишь почему?

Table 29.10. Two Equivalent Transforms

Без<proto::reverse_fold<>>

С<proto::reverse_fold<>>

FoldToList(
    proto::_left
  , FoldToList(proto::_right, proto::_state, proto::_data)
  , proto::_data
)

proto::reverse_fold<_, proto::_state, FoldToList>


Обработка выражений с произвольным количеством детей может быть болезненной. Что, если вы хотите что-то сделать с каждым ребенком, то передайте результаты в качестве аргументов какой-то другой функции? Можете ли вы сделать это один раз, не беспокоясь о том, сколько детей имеет выражение? Да. Вот где пригодятся выражения Прото. Распаковка выражений дает вам возможность писать вызывающие и объектные преобразования, которые обрабатываютn-арные выражения.

[Note]Note

Inspired by C++11 Variadic Templates

Выражения Proto в распаковке черпают вдохновение из одноименной функции C++11. Если вы знакомы с вариадными функциями и, в частности, с тем, как расширить набор параметров функции, это обсуждение должно показаться очень знакомым. Однако эта функция фактически не использует никаких функций C++11, поэтому описанный здесь код будет работать с любым совместимым компилятором C++98.

Example: A C++ Expression Evaluator

Proto имеет встроенное преобразование<proto::_default<>>для оценки выражений Proto на языке C++. Но если бы этого не произошло, было бы не слишком сложно реализовать его с нуля, используя шаблоны распаковки Proto. Преобразование<eval>ниже делает именно это.

// A callable polymorphic function object that takes an unpacked expression
// and a tag, and evaluates the expression. A plus tag and two operands adds
// them with operator +, for instance.
struct do_eval : proto::callable
{
    typedef double result_type;
#define UNARY_OP(TAG, OP)                                                       \
    template<typename Arg>                                                      \
    double operator()(proto::tag::TAG, Arg arg) const                           \
    {                                                                           \
        return OP arg;                                                          \
    }                                                                           \
    /**/
#define BINARY_OP(TAG, OP)                                                      \
    template<typename Left, typename Right>                                     \
    double operator()(proto::tag::TAG, Left left, Right right) const            \
    {                                                                           \
        return left OP right;                                                   \
    }                                                                           \
    /**/
    UNARY_OP(negate, -)
    BINARY_OP(plus, +)
    BINARY_OP(minus, -)
    BINARY_OP(multiplies, *)
    BINARY_OP(divides, /)
    /*... others ...*/
};
struct eval
  : proto::or_<
        // Evaluate terminals by simply returning their value
        proto::when<proto::terminal<_>, proto::_value>
        // Non-terminals are handled by unpacking the expression,
        // recursively calling eval on each child, and passing
        // the results along with the expression's tag to do_eval
        // defined above.
      , proto::otherwise<do_eval(proto::tag_of<_>(), eval(proto::pack(_))...)>
        // UNPACKING PATTERN HERE -------------------^^^^^^^^^^^^^^^^^^^^^^^^
    >
{};

Основная часть вышеупомянутого кода посвящена объекту функции<do_eval>, который отображает типы тегов на поведение, но интересным битом является определение алгоритма<eval>внизу. Терминалы обрабатываются довольно просто, но нетерминалы могут быть унарными, двоичными, троичными, дажеn-ary, если рассматривать функции вызова выражений. Алгоритм<eval>обрабатывает это равномерно с помощью шаблона распаковки.

Нетерминалы оцениваются с помощью этого вызывающего преобразования:

do_eval(proto::tag_of<_>(), eval(proto::pack(_))...)

Вы можете прочитать это так: позвоните объекту функции<do_eval>с тегом текущего выражения и всем его детям после того, как они были оценены с<eval>. Образец распаковки - это бит непосредственно перед эллипсом:<eval(proto::pack(_))>.

Вот что здесь происходит. Выражение распаковки повторяется один раз для каждого ребенка в оцениваемом выражении. В каждом повторении тип<proto::pack(_)>заменяется на<proto::_child_c<N>>. Итак, если унарное выражение передано<eval>, оно на самом деле оценивается так:

// After the unpacking pattern is expanded for a unary expression
do_eval(proto::tag_of<_>(), eval(proto::_child_c<0>))

И когда проходит двоичное выражение, шаблон распаковки расширяется следующим образом:

// After the unpacking pattern is expanded for a binary expression
do_eval(proto::tag_of<_>(), eval(proto::_child_c<0>), eval(proto::_child_c<1>))

Хотя это не может произойти в нашем примере, при прохождении терминала шаблон распаковки расширяется так, что он извлекает значение из терминала вместо детей. Так что с ним обращаются вот так:

// If a terminal were passed to this transform, Proto would try
// to evaluate it like this, which would fail:
do_eval(proto::tag_of<_>(), eval(proto::_value))

Это не имеет смысла.<proto::_value>вернул бы то, что не является выражением Прото, и<eval>не смог бы его оценить. Алгоритмы Proto не работают, если вы не передаете им выражения Proto.

[Note]Note

Kickin' It Old School

Вы можете подумать, мой компилятор не поддерживает шаблоны C++11! Как это может сработать? Ответ прост:<...>выше не является расширением пакета C++11. На самом деле это варарг в стиле старой школы. Помните, что вызывающие и объектные преобразования являютсятипами функций. Трансформация с одним из этих псевдопакетных расширений — это просто скучная, старая функция Варарга. Прото просто интерпретирует это по-другому.

Распаковочные узоры очень выразительны. Любое вызывающее или объектное преобразование может быть использовано в качестве распаковочного рисунка, если<proto::pack(_)>появляется точно один раз где-то внутри него. Это дает вам большую гибкость в том, как вы хотите обрабатывать детей выражения, прежде чем передавать их какому-либо функциональному объекту или конструктору объектов.

[Note]Note

Это продвинутая тема, которая нужна только людям, определяющим большие EDSL. Не стесняйтесь пропустить это, если вы только начинаете с Proto.

До сих пор мы видели примеры грамматики со встроенными преобразованиями. На практике грамматики могут быть довольно большими, и вы можете использовать их для управления несколькими различными вычислениями. Например, у вас может быть грамматика для линейной области алгебры, и вы можете использовать ее для вычисления формы результата (вектора или матрицы?), а также для оптимального вычисления результата. Вам не нужно копировать и вставлять весь шебанг, чтобы настроить одно из встроенных преобразований. Вместо этого вам нужно определить грамматику один раз и указать преобразования позже, когда вы будете готовы оценить выражение. Для этого вы используетевнешние преобразования. Модель, которую вы будете использовать, такова: заменить одно или несколько преобразований в вашей грамматике специальным заполнителем<proto::external_transform>. Затем вы создадите набор преобразований, которые вы перейдете к грамматике в параметре данных (3-й параметр после выражения и состояния) при оценке.

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

#include <boost/assert.hpp>
#include <boost/mpl/int.hpp>
#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/container/generation/make_vector.hpp>
#include <boost/proto/proto.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
namespace fusion = boost::fusion;
// The argument placeholder type
template<typename I> struct placeholder : I {};
// The grammar for valid calculator expressions
struct calc_grammar
  : proto::or_<
        proto::terminal<placeholder<proto::_> >
      , proto::terminal<int>
      , proto::plus<calc_grammar, calc_grammar>
      , proto::minus<calc_grammar, calc_grammar>
      , proto::multiplies<calc_grammar, calc_grammar>
      , proto::divides<calc_grammar, calc_grammar>
    >
{};
template<typename E> struct calc_expr;
struct calc_domain : proto::domain<proto::generator<calc_expr> > {};
template<typename E>
struct calc_expr
  : proto::extends<E, calc_expr<E>, calc_domain>
{
    calc_expr(E const &e = E()) : calc_expr::proto_extends(e) {}
};
calc_expr<proto::terminal<placeholder<mpl::int_<0> > >::type> _1;
calc_expr<proto::terminal<placeholder<mpl::int_<1> > >::type> _2;
int main()
{
    // Build a calculator expression, and do nothing with it.
    (_1 + _2);
}

Теперь давайте вставим преобразования в<calc_grammar>, чтобы мы могли использовать его для оценки выражений калькулятора:

// The calculator grammar with embedded transforms for evaluating expression.
struct calc_grammar
  : proto::or_<
        proto::when<
            proto::terminal<placeholder<proto::_> >
          , proto::functional::at(proto::_state, proto::_value)
        >
      , proto::when<
            proto::terminal<int>
          , proto::_value
        >
      , proto::when<
            proto::plus<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
      , proto::when<
            proto::minus<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
      , proto::when<
            proto::multiplies<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
      , proto::when<
            proto::divides<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
    >
{};

С помощью этого определения<calc_grammar>мы можем оценить выражения, пройдя по вектору Fusion, содержащему значения, используемые для держателей<_1>и<_2>:

int result = calc_grammar()(_1 + _2, fusion::make_vector(3, 4));
BOOST_ASSERT(result == 7);

Мы также хотим альтернативную стратегию оценки, которая проверяет деление на ноль и делает исключение. Как нелепо было бы копировать все<calc_grammar>, просто чтобы изменить одну линию, которая преобразует выражения деления?! Внешние преобразования идеально подходят для этой проблемы.

Во-первых, мы даем правилу деления в нашей грамматике «имя», то есть делаем его структурой. Позже мы используем этот уникальный тип для отправки правильных преобразований.

struct calc_grammar;
struct divides_rule : proto::divides<calc_grammar, calc_grammar> {};

Затем мы меняем<calc_grammar>, чтобы сделать обработку выражений деления внешней.

// The calculator grammar with an external transform for evaluating
// division expressions.
struct calc_grammar
  : proto::or_<
        /* ... as before ... */
      , proto::when<
            divides_rule
          , proto::external_transform
        >
    >
{};

Использование<proto::external_transform>выше делает обработку выражений деления внешне параметризируемой.

Далее мы используем<proto::external_transforms<>>(обратите внимание на следящие «s»), чтобы захватить нашу стратегию оценки в пакет, который мы можем передать преобразованию в параметре данных. Читайте дальше для объяснения.

// Evaluate division nodes as before
struct non_checked_division
  : proto::external_transforms<
        proto::when< divides_rule, proto::_default<calc_grammar> >
    >
{};
/* ... */
non_checked_division non_checked;
int result2 = calc_grammar()(_1 / _2, fusion::make_vector(6, 2), non_checked);

Структура<non_cecked_division>связывает преобразование<proto::_default<calc_grammar>>с правилом грамматики<divides_rule>. Пример этой структуры передается в качестве третьего параметра при вызове<calc_grammar>.

Теперь, давайте реализуем проверенное подразделение. Остальное должно быть неудивительно.

struct division_by_zero : std::exception {};
struct do_checked_divide : proto::callable
{
    typedef int result_type;
    int operator()(int left, int right) const
    {
        if (right == 0) throw division_by_zero();
        return left / right;
    }
};
struct checked_division
  : proto::external_transforms<
        proto::when<
            divides_rule
          , do_checked_divide(calc_grammar(proto::_left), calc_grammar(proto::_right))
        >
    >
{};
/* ... */
try
{
    checked_division checked;
    int result3 = calc_grammar_extern()(_1 / _2, fusion::make_vector(6, 0), checked);
}
catch(division_by_zero)
{
    std::cout << "caught division by zero!\n";
}

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

Separating Data From External Transforms

Как описано выше, функция внешних преобразований узурпирует параметр данных, который предназначен для того, чтобы быть местом, где можно передавать произвольные данные, и придает ему определенный смысл. Но что, если вы уже используете параметр данных для чего-то другого? Ответ заключается в использовании трансформирующей среды. Связывая внешние преобразования с ключом<proto::transforms>, вы можете передавать произвольные данные в других слотах.

Чтобы продолжить приведенный выше пример, что, если нам также необходимо передать часть данных в наше преобразование вместе с внешними преобразованиями? Это будет выглядеть так:

int result3 = calc_grammar_extern()(
    _1 / _2
  , fusion::make_vector(6, 0)
  , (proto::data = 42, proto::transforms = checked)
);

В приведенном выше вызове алгоритма<calc_grammar_extern>карта внешних преобразований связана с ключом<proto::transforms>и передается алгоритму в среде преобразования. Также в среде преобразования находится пара ключ/значение, которая связывает значение<42>с ключом<proto::data>.

Примитивные преобразования являются строительными блоками для более интересных составных преобразований. Прото определяет кучу полезных примитивных преобразований. Они кратко излагаются ниже.

proto::_value

При наличии терминального выражения возвращайте значение терминала.

proto::_child_c<>

Учитывая не терминальное выражение,<proto::_child_c<N>>возвращает<N>— ребенка.

proto::_child

Синонимы к<proto::_child_c<0>>.

proto::_left

Синонимы к<proto::_child_c<0>>.

proto::_right

Синонимы к<proto::_child_c<1>>.

proto::_expr

Возвращает текущее выражение без изменений.

proto::_state

Возвращает текущее состояние без изменений.

proto::_data

Возвращает текущие данные без изменений.

proto::call<>

Для данного вызывающего преобразования<CT>,<proto::call<CT>>превращает вызывающее преобразование в примитивное преобразование. Это полезно для разбора вызывающих преобразований из преобразований объектов, а также для работы с ошибками компилятора с вложенными типами функций.

proto::make<>

Для данного объекта преобразование<OT>,<proto::make<OT>>превращает объект преобразование в примитивное преобразование. Это полезно для раздвоения преобразований объектов из вызывающих преобразований, а также для работы с ошибками компилятора с вложенными типами функций.

proto::_default<>

Учитывая грамматику<G>,прото::_default<<G>>оценивает текущий узел в соответствии со стандартным значением C++ операции, которую представляет узел. Например, если текущий узел представляет собой двоичный плюс узел, оба ребенка будут оценены в соответствии с<G>, и результаты будут добавлены и возвращены. Тип возврата выводится с помощью Boost. Тип библиотеки.

proto::fold<>

Учитывая три преобразования<ET>,<ST>и<FT>,<proto::fold<ET>,,>сначала оценивает<ET>для получения начального состояния для сгиба, а затем оценивает<ST>для каждого элемента в последовательности, чтобы генерировать следующее состояние из предыдущего.

proto::reverse_fold<>

Как и<proto::fold<>>, за исключением элементов в последовательности Fusion, они повторяются в обратном порядке.

proto::fold_tree<>

Как<proto::fold<ET>,<ST>,<FT>>, за исключением того, что результат<ET>преобразование рассматривается как дерево выражения, которое сплющенодля генерации последовательности, которая должна быть сложена. Сплющивание дерева экспрессии приводит к тому, что узлы ребенка с тем же типом тега, что и родитель, вводятся в последовательность. Например,<a >>b >>c>будет сплющен на последовательность [<a>,<b>,<c>], и эта последовательность будет сложена.

proto::reverse_fold_tree<>

Как и<proto::fold_tree<>>, за исключением того, что сплющенная последовательность повторяется в обратном порядке.

proto::lazy<>

Комбинация<proto::make<>>и<proto::call<>>, которая полезна, когда характер преобразования зависит от экспрессии, состояния и/или параметров данных.<proto::lazy<R(A0,A1...An)>>сначала оценивает<proto::make<R()>>для вычисления вызывающего типа<R2>. Затем он оценивает<proto::call<R2(A0,A1...An)>>.

All Grammars Are Primitive Transforms

В дополнение к вышеупомянутым примитивным преобразованиям, все элементы грамматики Прото также являются примитивными преобразованиями. Их поведение описано ниже.

proto::_

Верните текущее выражение без изменений.

proto::or_<>

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

proto::and_<>

Для данного набора подграмм применяют все связанные с ним преобразования и возвращают результат последнего.

proto::not_<>

Верните текущее выражение без изменений.

proto::if_<>

Учитывая три преобразования, оцените первое и отнеситесь к результату как к булевому значению времени компиляции. Если это так, то оцените второе преобразование. В противном случае оцените третью.

proto::switch_<>

Как и в 2014 году, найдите подграмму, которая соответствует данному выражению, и примените связанное с ним преобразование.

proto::terminal<>

Возврат текущего терминального выражения без изменений.

proto::plus<>, proto::nary_expr<>, et. al.

Протограмматика, которая соответствует нетерминалу, такому как<proto::plus<G0>,<G1>>, при использовании в качестве примитивного преобразования, создает новый узел плюс, где левый ребенок трансформируется в соответствии с<G0>и правый ребенок с<G1>.

The Pass-Through Transform

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

proto::function< X, proto::vararg<Y> >

... соответствует функциональным выражениям, где первый ребенок соответствует грамматике<X>, а остальные соответствуют грамматике<Y>. При использовании в качестве преобразования грамматика создает новое функциональное выражение, где первый ребенок трансформируется в соответствии с<X>, а остальные трансформируются в соответствии с<Y>.

Следующие шаблоны классов в Proto можно использовать в качестве грамматики с переходными преобразованиями:

Table 29.11. Class Templates With Pass-Through Transforms

Шаблоны с переходными трансформациями

<proto::unary_plus<>>

<proto::negate<>>

<proto::dereference<>>

<proto::complement<>>

<proto::address_of<>>

<proto::logical_not<>>

<proto::pre_inc<>>

<proto::pre_dec<>>

<proto::post_inc<>>

<proto::post_dec<>>

<proto::shift_left<>>

<proto::shift_right<>>

<proto::multiplies<>>

<proto::divides<>>

<proto::modulus<>>

<proto::plus<>>

<proto::minus<>>

<proto::less<>>

<proto::greater<>>

<proto::less_equal<>>

<proto::greater_equal<>>

<proto::equal_to<>>

<proto::not_equal_to<>>

<proto::logical_or<>>

<proto::logical_and<>>

<proto::bitwise_and<>>

<proto::bitwise_or<>>

<proto::bitwise_xor<>>

<proto::comma<>>

<proto::mem_ptr<>>

<proto::assign<>>

<proto::shift_left_assign<>>

<proto::shift_right_assign<>>

<proto::multiplies_assign<>>

<proto::divides_assign<>>

<proto::modulus_assign<>>

<proto::plus_assign<>>

<proto::minus_assign<>>

proto::bitwise_and_assign<>

<proto::bitwise_or_assign<>>

<proto::bitwise_xor_assign<>>

<proto::subscript<>>

<proto::if_else_<>>

<proto::function<>>

<proto::unary_expr<>>

<proto::binary_expr<>>

<proto::nary_expr<>>


The Many Roles of Proto Operator Metafunctions

Мы видели, что такие шаблоны, как<proto::terminal<>>,<proto::plus<>>и<proto::nary_expr<>>, заполняют многие роли. Это метафункция, которая генерирует типы экспрессии. Это грамматики, которые соответствуют типам выражения. Это примитивные преобразования. Следующие образцы кода показывают примеры каждого из них.

As Metafunctions ...

// proto::terminal<> and proto::plus<> are metafunctions
// that generate expression types:
typedef proto::terminal<int>::type int_;
typedef proto::plus<int_, int_>::type plus_;
int_ i = {42}, j = {24};
plus_ p = {i, j};

As Grammars ...

// proto::terminal<> and proto::plus<> are grammars that
// match expression types
struct Int : proto::terminal<int> {};
struct Plus : proto::plus<Int, Int> {};
BOOST_MPL_ASSERT(( proto::matches< int_, Int > ));
BOOST_MPL_ASSERT(( proto::matches< plus_, Plus > ));

As Primitive Transforms ...

// A transform that removes all unary_plus nodes in an expression
struct RemoveUnaryPlus
  : proto::or_<
        proto::when<
            proto::unary_plus<RemoveUnaryPlus>
          , RemoveUnaryPlus(proto::_child)
        >
        // Use proto::terminal<> and proto::nary_expr<>
        // both as grammars and as primitive transforms.
      , proto::terminal<_>
      , proto::nary_expr<_, proto::vararg<RemoveUnaryPlus> >
    >
{};
int main()
{
    proto::literal<int> i(0);
    proto::display_expr(
        +i - +(i - +i)
    );
    proto::display_expr(
        RemoveUnaryPlus()( +i - +(i - +i) )
    );
}

Вышеприведенный код отображает следующее, которое показывает, что унари плюс узлы были лишены выражения:

minus(
    unary_plus(
        terminal(0)
    )
  , unary_plus(
        minus(
            terminal(0)
          , unary_plus(
                terminal(0)
            )
        )
    )
)
minus(
    terminal(0)
  , minus(
        terminal(0)
      , terminal(0)
    )
)

В предыдущих разделах мы видели, как составлять большие преобразования из меньших преобразований с использованием типов функций. Меньшие преобразования, из которых состоят более крупные преобразования, являютсяпримитивными преобразованиями, и Прото обеспечивает кучу общих, таких как<_child0>и<_value>. В этом разделе мы рассмотрим, как создать свои собственные примитивные преобразования.

[Note]Note

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

Примитивные преобразования наследуют от<proto::transform<>>и имеют вложенный<impl<>>шаблон, который наследует от<proto::transform_impl<>>. Например, именно так Прото определяет<_child_c<N>>преобразование, возвращающее<N>— дитя текущего выражения:

namespace boost { namespace proto
{
    // A primitive transform that returns N-th child
    // of the current expression.
    template<int N>
    struct _child_c : transform<_child_c<N> >
    {
        template<typename Expr, typename State, typename Data>
        struct impl : transform_impl<Expr, State, Data>
        {
            typedef
                typename result_of::child_c<Expr, N>::type
            result_type;
            result_type operator ()(
                typename impl::expr_param expr
              , typename impl::state_param state
              , typename impl::data_param data
            ) const
            {
                return proto::child_c<N>(expr);
            }
        };
    };
    // Note that _child_c<N> is callable, so that
    // it can be used in callable transforms, as:
    //   _child_c<0>(_child_c<1>)
    template<int N>
    struct is_callable<_child_c<N> >
      : mpl::true_
    {};
}}

Базовый класс<proto::transform<>>обеспечивает перегрузки<operator()>и вложенный шаблон<result<>>, которые делают ваше преобразование действительным функциональным объектом. Они реализованы с точки зрения вложенного шаблона<impl<>>, который вы определяете.

Базовый класс<proto::transform_impl<>>— удобство. Он предоставляет некоторые вложенные типдефы, которые обычно полезны. Они указаны в таблице ниже:

Table 29.12. proto::transform_impl<Expr, State, Data> typedefs

печатать

эквивалентный

<expr>

<typenameremove_reference<Expr>::type>

state

<typenameremove_reference<State>::type>

<data>

<typenameremove_reference<Data>::type>

<expr_param>

<typenameadd_reference<typename add_const<Expr>::type>::type>

<state_param>

<typenameadd_reference<typename add_const<State>::type>::type>

<data_param>

<typenameadd_reference<typename add_const<Data>::type>::type>


Вы заметите, что<_child_c::impl::operator()>принимает аргументы типов<expr_param>,<state_param>и<data_param>. Типдефы позволяют легко принимать аргументы путем ссылки или соответственно ссылки.

Единственный интересный бит — специализация<is_callable<>>, которая будет описана вследующем разделе.

Преобразования обычно имеют форму<proto::when<Something,R(A0,A1,...)>>. Вопрос в том, представляет ли<R>функцию вызова или объект для построения, и ответ определяет, как<proto::when<>>оценивает преобразование.<proto::when<>>использует<proto::is_callable<>>черту, чтобы разъяснить между ними. Proto делает все возможное, чтобы угадать, можно ли назвать тип или нет, но это не всегда правильно. Лучше всего знать правила использования Proto, чтобы вы знали, когда вам нужно быть более откровенным.

Для большинства типов<R>,<proto::is_callable<R>>проверяет наследование<proto::callable>. Однако, если тип<R>является шаблонной специализацией, Прото предполагает, что оннеможет называться, даже если шаблон наследует от<proto::callable>. Посмотрим, почему через минуту. Рассмотрим следующий ошибочный вызывающий объект:

// Proto can't tell this defines something callable!
template<typename T>
struct times2 : proto::callable
{
    typedef T result_type;
    T operator()(T i) const
    {
        return i * 2;
    }
};
// ERROR! This is not going to multiply the int by 2:
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2<int>(proto::_value)
    >
{};

Проблема в том, что Прото не знает, что<times2<int>>вызывается, поэтому, скорее, ссылаясь на объект функции<times2<int>>, Прото попытается построить объект<times2<int>>и инициализировать его<int>. Это не будет компиляцией.

[Note]Note

Почему Прото не может сказать, что<times2<int>>можно вызвать? Ведь он наследует от<proto::callable>, и это можно обнаружить, верно? Проблема заключается в том, что простой вопрос о том, наследует ли какой-либо тип<X<Y>>от<callable>, приведет к тому, что шаблон<X<Y>>будет реализован. Это проблема для такого типа, как<std::vector<_value(_child1)>>.<std::vector<>>не пострадает от того, чтобы быть реализованным с<_value(_child1)>в качестве параметра шаблона. Поскольку простое задание вопроса иногда приводит к трудной ошибке, Прото не может спросить; он должен предположить, что<X<Y>>представляет собой объект для построения, а не функцию для вызова.

Есть несколько вариантов решения проблемы<times2<int>>. Одно из решений состоит в том, чтобы обернуть преобразование в<proto::call<>>. Это заставляет Прото относиться к<times2<int>>как к вызывающему:

// OK, calls times2<int>
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , proto::call<times2<int>(proto::_value)>
    >
{};

Это может быть немного больно, потому что нам нужно обернуть каждое использование<times2<int>>, которое может быть утомительным и склонным к ошибкам, и делает нашу грамматику загроможденной и трудно читаемой.

Другое решение — специализироваться<proto::is_callable<>>на нашем<times2<>>шаблоне:

namespace boost { namespace proto
{
    // Tell Proto that times2<> is callable
    template<typename T>
    struct is_callable<times2<T> >
      : mpl::true_
    {};
}}
// OK, times2<> is callable
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2<int>(proto::_value)
    >
{};

Это лучше, но все же боль из-за необходимости открыть пространство имен Прото.

Вы можете просто убедиться, что вызывающий тип не является шаблонной специализацией. Рассмотрим следующее:

// No longer a template specialization!
struct times2int : times2<int> {};
// OK, times2int is callable
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2int(proto::_value)
    >
{};

Это работает, потому что теперь Прото может сказать, что<times2int>наследует (косвенно) от<proto::callable>. Любые типы, не являющиеся шаблонами, могут быть безопасно проверены на наследование, потому что, поскольку они не являются шаблонами, не стоит беспокоиться об ошибках.

Есть один последний способ сказать Прото, что<times2<>>можно вызвать. Вы можете добавить дополнительный параметр шаблона, который по умолчанию<proto::callable>:

// Proto will recognize this as callable
template<typename T, typename Callable = proto::callable>
struct times2 : proto::callable
{
    typedef T result_type;
    T operator()(T i) const
    {
        return i * 2;
    }
};
// OK, this works!
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2<int>(proto::_value)
    >
{};

Обратите внимание, что в дополнение к дополнительному параметру шаблона<times2<>>все еще наследуется от<proto::callable>. Это не обязательно в этом примере, но это хороший стиль, потому что любые типы, полученные из<times2<>>(как<times2int>определено выше), все равно будут считаться вызывающими.

Пример кода стоит тысячи слов.

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

////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <iostream>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
// This #include is only needed for compilers that use typeof emulation:
#include <boost/typeof/std/ostream.hpp>
namespace proto = boost::proto;
proto::terminal< std::ostream & >::type cout_ = {std::cout};
template< typename Expr >
void evaluate( Expr const & expr )
{
    proto::default_context ctx;
    proto::eval(expr, ctx);
}
int main()
{
    evaluate( cout_ << "hello" << ',' << " world" );
    return 0;
}

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

//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is a simple example of how to build an arithmetic expression
// evaluator with placeholders.
#include <iostream>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
namespace proto = boost::proto;
using proto::_;
template<int I> struct placeholder {};
// Define some placeholders
proto::terminal< placeholder< 1 > >::type const _1 = {{}};
proto::terminal< placeholder< 2 > >::type const _2 = {{}};
// Define a calculator context, for evaluating arithmetic expressions
struct calculator_context
  : proto::callable_context< calculator_context const >
{
    // The values bound to the placeholders
    double d[2];
    // The result of evaluating arithmetic expressions
    typedef double result_type;
    explicit calculator_context(double d1 = 0., double d2 = 0.)
    {
        d[0] = d1;
        d[1] = d2;
    }
    // Handle the evaluation of the placeholder terminals
    template<int I>
    double operator ()(proto::tag::terminal, placeholder<I>) const
    {
        return d[ I - 1 ];
    }
};
template<typename Expr>
double evaluate( Expr const &expr, double d1 = 0., double d2 = 0. )
{
    // Create a calculator context with d1 and d2 substituted for _1 and _2
    calculator_context const ctx(d1, d2);
    // Evaluate the calculator expression with the calculator_context
    return proto::eval(expr, ctx);
}
int main()
{
    // Displays "5"
    std::cout << evaluate( _1 + 2.0, 3.0 ) << std::endl;
    // Displays "6"
    std::cout << evaluate( _1 * _2, 3.0, 2.0 ) << std::endl;
    // Displays "0.5"
    std::cout << evaluate( (_1 - _2) / _2, 3.0, 2.0 ) << std::endl;
    return 0;
}

Расширение примера Calc1, который использует<proto::extends<>>для создания выражений калькулятора допустимых объектов функций, которые могут быть использованы с алгоритмами STL.

//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This example enhances the simple arithmetic expression evaluator
// in calc1.cpp by using proto::extends to make arithmetic
// expressions immediately evaluable with operator (), a-la a
// function object
#include <iostream>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
namespace proto = boost::proto;
using proto::_;
template<typename Expr>
struct calculator_expression;
// Tell proto how to generate expressions in the calculator_domain
struct calculator_domain
  : proto::domain<proto::generator<calculator_expression> >
{};
// Will be used to define the placeholders _1 and _2
template<int I> struct placeholder {};
// Define a calculator context, for evaluating arithmetic expressions
// (This is as before, in calc1.cpp)
struct calculator_context
  : proto::callable_context< calculator_context const >
{
    // The values bound to the placeholders
    double d[2];
    // The result of evaluating arithmetic expressions
    typedef double result_type;
    explicit calculator_context(double d1 = 0., double d2 = 0.)
    {
        d[0] = d1;
        d[1] = d2;
    }
    // Handle the evaluation of the placeholder terminals
    template<int I>
    double operator ()(proto::tag::terminal, placeholder<I>) const
    {
        return d[ I - 1 ];
    }
};
// Wrap all calculator expressions in this type, which defines
// operator () to evaluate the expression.
template<typename Expr>
struct calculator_expression
  : proto::extends<Expr, calculator_expression<Expr>, calculator_domain>
{
    explicit calculator_expression(Expr const &expr = Expr())
      : calculator_expression::proto_extends(expr)
    {}
    BOOST_PROTO_EXTENDS_USING_ASSIGN(calculator_expression<Expr>)
    // Override operator () to evaluate the expression
    double operator ()() const
    {
        calculator_context const ctx;
        return proto::eval(*this, ctx);
    }
    double operator ()(double d1) const
    {
        calculator_context const ctx(d1);
        return proto::eval(*this, ctx);
    }
    double operator ()(double d1, double d2) const
    {
        calculator_context const ctx(d1, d2);
        return proto::eval(*this, ctx);
    }
};
// Define some placeholders (notice they're wrapped in calculator_expression<>)
calculator_expression<proto::terminal< placeholder< 1 > >::type> const _1;
calculator_expression<proto::terminal< placeholder< 2 > >::type> const _2;
// Now, our arithmetic expressions are immediately executable function objects:
int main()
{
    // Displays "5"
    std::cout << (_1 + 2.0)( 3.0 ) << std::endl;
    // Displays "6"
    std::cout << ( _1 * _2 )( 3.0, 2.0 ) << std::endl;
    // Displays "0.5"
    std::cout << ( (_1 - _2) / _2 )( 3.0, 2.0 ) << std::endl;
    return 0;
}

Расширение примера Calc2, использующее преобразование Прото для вычисления аритмии выражения калькулятора и статически утверждающее, что принято правильное число аргументов.

//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This example enhances the arithmetic expression evaluator
// in calc2.cpp by using a proto transform to calculate the
// number of arguments an expression requires and using a
// compile-time assert to guarantee that the right number of
// arguments are actually specified.
#include <iostream>
#include <boost/mpl/int.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/min_max.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
#include <boost/proto/transform.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
using proto::_;
// Will be used to define the placeholders _1 and _2
template<typename I> struct placeholder : I {};
// This grammar basically says that a calculator expression is one of:
//   - A placeholder terminal
//   - Some other terminal
//   - Some non-terminal whose children are calculator expressions
// In addition, it has transforms that say how to calculate the
// expression arity for each of the three cases.
struct CalculatorGrammar
  : proto::or_<
        // placeholders have a non-zero arity ...
        proto::when< proto::terminal< placeholder<_> >, proto::_value >
        // Any other terminals have arity 0 ...
      , proto::when< proto::terminal<_>, mpl::int_<0>() >
        // For any non-terminals, find the arity of the children and
        // take the maximum. This is recursive.
      , proto::when< proto::nary_expr<_, proto::vararg<_> >
             , proto::fold<_, mpl::int_<0>(), mpl::max<CalculatorGrammar, proto::_state>() > >
    >
{};
// Simple wrapper for calculating a calculator expression's arity.
// It specifies mpl::int_<0> as the initial state. The data, which
// is not used, is mpl::void_.
template<typename Expr>
struct calculator_arity
  : boost::result_of<CalculatorGrammar(Expr)>
{};
template<typename Expr>
struct calculator_expression;
// Tell proto how to generate expressions in the calculator_domain
struct calculator_domain
  : proto::domain<proto::generator<calculator_expression> >
{};
// Define a calculator context, for evaluating arithmetic expressions
// (This is as before, in calc1.cpp and calc2.cpp)
struct calculator_context
  : proto::callable_context< calculator_context const >
{
    // The values bound to the placeholders
    double d[2];
    // The result of evaluating arithmetic expressions
    typedef double result_type;
    explicit calculator_context(double d1 = 0., double d2 = 0.)
    {
        d[0] = d1;
        d[1] = d2;
    }
    // Handle the evaluation of the placeholder terminals
    template<typename I>
    double operator ()(proto::tag::terminal, placeholder<I>) const
    {
        return d[ I() - 1 ];
    }
};
// Wrap all calculator expressions in this type, which defines
// operator () to evaluate the expression.
template<typename Expr>
struct calculator_expression
  : proto::extends<Expr, calculator_expression<Expr>, calculator_domain>
{
    typedef
        proto::extends<Expr, calculator_expression<Expr>, calculator_domain>
    base_type;
    explicit calculator_expression(Expr const &expr = Expr())
      : base_type(expr)
    {}
    BOOST_PROTO_EXTENDS_USING_ASSIGN(calculator_expression<Expr>)
    // Override operator () to evaluate the expression
    double operator ()() const
    {
        // Assert that the expression has arity 0
        BOOST_MPL_ASSERT_RELATION(0, ==, calculator_arity<Expr>::type::value);
        calculator_context const ctx;
        return proto::eval(*this, ctx);
    }
    double operator ()(double d1) const
    {
        // Assert that the expression has arity 1
        BOOST_MPL_ASSERT_RELATION(1, ==, calculator_arity<Expr>::type::value);
        calculator_context const ctx(d1);
        return proto::eval(*this, ctx);
    }
    double operator ()(double d1, double d2) const
    {
        // Assert that the expression has arity 2
        BOOST_MPL_ASSERT_RELATION(2, ==, calculator_arity<Expr>::type::value);
        calculator_context const ctx(d1, d2);
        return proto::eval(*this, ctx);
    }
};
// Define some placeholders (notice they're wrapped in calculator_expression<>)
calculator_expression<proto::terminal< placeholder< mpl::int_<1> > >::type> const _1;
calculator_expression<proto::terminal< placeholder< mpl::int_<2> > >::type> const _2;
// Now, our arithmetic expressions are immediately executable function objects:
int main()
{
    // Displays "5"
    std::cout << (_1 + 2.0)( 3.0 ) << std::endl;
    // Displays "6"
    std::cout << ( _1 * _2 )( 3.0, 2.0 ) << std::endl;
    // Displays "0.5"
    std::cout << ( (_1 - _2) / _2 )( 3.0, 2.0 ) << std::endl;
    // This won't compile because the arity of the
    // expression doesn't match the number of arguments
    // ( (_1 - _2) / _2 )( 3.0 );
    return 0;
}

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

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

///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This example constructs a mini-library for linear algebra, using
// expression templates to eliminate the need for temporaries when
// adding vectors of numbers.
//
// This example uses a domain with a grammar to prune the set
// of overloaded operators. Only those operators that produce
// valid lazy vector expressions are allowed.
#include <vector>
#include <iostream>
#include <boost/mpl/int.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
using proto::_;
template<typename Expr>
struct lazy_vector_expr;
// This grammar describes which lazy vector expressions
// are allowed; namely, vector terminals and addition
// and subtraction of lazy vector expressions.
struct LazyVectorGrammar
  : proto::or_<
        proto::terminal< std::vector<_> >
      , proto::plus< LazyVectorGrammar, LazyVectorGrammar >
      , proto::minus< LazyVectorGrammar, LazyVectorGrammar >
    >
{};
// Tell proto that in the lazy_vector_domain, all
// expressions should be wrapped in laxy_vector_expr<>
// and must conform to the lazy vector grammar.
struct lazy_vector_domain
  : proto::domain<proto::generator<lazy_vector_expr>, LazyVectorGrammar>
{};
// Here is an evaluation context that indexes into a lazy vector
// expression, and combines the result.
template<typename Size = std::size_t>
struct lazy_subscript_context
{
    lazy_subscript_context(Size subscript)
      : subscript_(subscript)
    {}
    // Use default_eval for all the operations ...
    template<typename Expr, typename Tag = typename Expr::proto_tag>
    struct eval
      : proto::default_eval<Expr, lazy_subscript_context>
    {};
    // ... except for terminals, which we index with our subscript
    template<typename Expr>
    struct eval<Expr, proto::tag::terminal>
    {
        typedef typename proto::result_of::value<Expr>::type::value_type result_type;
        result_type operator ()( Expr const & expr, lazy_subscript_context & ctx ) const
        {
            return proto::value( expr )[ ctx.subscript_ ];
        }
    };
    Size subscript_;
};
// Here is the domain-specific expression wrapper, which overrides
// operator [] to evaluate the expression using the lazy_subscript_context.
template<typename Expr>
struct lazy_vector_expr
  : proto::extends<Expr, lazy_vector_expr<Expr>, lazy_vector_domain>
{
    lazy_vector_expr( Expr const & expr = Expr() )
      : lazy_vector_expr::proto_extends( expr )
    {}
    // Use the lazy_subscript_context<> to implement subscripting
    // of a lazy vector expression tree.
    template< typename Size >
    typename proto::result_of::eval< Expr, lazy_subscript_context<Size> >::type
    operator []( Size subscript ) const
    {
        lazy_subscript_context<Size> ctx(subscript);
        return proto::eval(*this, ctx);
    }
};
// Here is our lazy_vector terminal, implemented in terms of lazy_vector_expr
template< typename T >
struct lazy_vector
  : lazy_vector_expr< typename proto::terminal< std::vector<T> >::type >
{
    typedef typename proto::terminal< std::vector<T> >::type expr_type;
    lazy_vector( std::size_t size = 0, T const & value = T() )
      : lazy_vector_expr<expr_type>( expr_type::make( std::vector<T>( size, value ) ) )
    {}
    // Here we define a += operator for lazy vector terminals that
    // takes a lazy vector expression and indexes it. expr[i] here
    // uses lazy_subscript_context<> under the covers.
    template< typename Expr >
    lazy_vector &operator += (Expr const & expr)
    {
        std::size_t size = proto::value(*this).size();
        for(std::size_t i = 0; i < size; ++i)
        {
            proto::value(*this)[i] += expr[i];
        }
        return *this;
    }
};
int main()
{
    // lazy_vectors with 4 elements each.
    lazy_vector< double > v1( 4, 1.0 ), v2( 4, 2.0 ), v3( 4, 3.0 );
    // Add two vectors lazily and get the 2nd element.
    double d1 = ( v2 + v3 )[ 2 ];   // Look ma, no temporaries!
    std::cout << d1 << std::endl;
    // Subtract two vectors and add the result to a third vector.
    v1 += v2 - v3;                  // Still no temporaries!
    std::cout << '{' << v1[0] << ',' << v1[1]
              << ',' << v1[2] << ',' << v1[3] << '}' << std::endl;
    // This expression is disallowed because it does not conform
    // to the LazyVectorGrammar
    //(v2 + v3) += v1;
    return 0;
}

Это простой пример произвольных манипуляций с трансформациями Proto. Он принимает некоторое выражение, включающее первичные цвета, и объединяет цвета в соответствии с произвольными правилами. Это порт примера RGB изPETE.

///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is a simple example of doing arbitrary type manipulations with proto
// transforms. It takes some expression involving primary colors and combines
// the colors according to arbitrary rules. It is a port of the RGB example
// from PETE (http://www.codesourcery.com/pooma/download.html).
#include <iostream>
#include <boost/proto/core.hpp>
#include <boost/proto/transform.hpp>
namespace proto = boost::proto;
struct RedTag
{
    friend std::ostream &operator <<(std::ostream &sout, RedTag)
    {
        return sout << "This expression is red.";
    }
};
struct BlueTag
{
    friend std::ostream &operator <<(std::ostream &sout, BlueTag)
    {
        return sout << "This expression is blue.";
    }
};
struct GreenTag
{
    friend std::ostream &operator <<(std::ostream &sout, GreenTag)
    {
        return sout << "This expression is green.";
    }
};
typedef proto::terminal<RedTag>::type RedT;
typedef proto::terminal<BlueTag>::type BlueT;
typedef proto::terminal<GreenTag>::type GreenT;
struct Red;
struct Blue;
struct Green;
///////////////////////////////////////////////////////////////////////////////
// A transform that produces new colors according to some arbitrary rules:
// red & green give blue, red & blue give green, blue and green give red.
struct Red
  : proto::or_<
        proto::plus<Green, Blue>
      , proto::plus<Blue, Green>
      , proto::plus<Red, Red>
      , proto::terminal<RedTag>
    >
{};
struct Green
  : proto::or_<
        proto::plus<Red, Blue>
      , proto::plus<Blue, Red>
      , proto::plus<Green, Green>
      , proto::terminal<GreenTag>
    >
{};
struct Blue
  : proto::or_<
        proto::plus<Red, Green>
      , proto::plus<Green, Red>
      , proto::plus<Blue, Blue>
      , proto::terminal<BlueTag>
    >
{};
struct RGB
  : proto::or_<
        proto::when< Red, RedTag() >
      , proto::when< Blue, BlueTag() >
      , proto::when< Green, GreenTag() >
    >
{};
template<typename Expr>
void printColor(Expr const & expr)
{
    int i = 0; // dummy state and data parameter, not used
    std::cout << RGB()(expr, i, i) << std::endl;
}
int main()
{
    printColor(RedT() + GreenT());
    printColor(RedT() + GreenT() + BlueT());
    printColor(RedT() + (GreenT() + BlueT()));
    return 0;
}

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

///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This example constructs a mini-library for linear algebra, using
// expression templates to eliminate the need for temporaries when
// adding arrays of numbers. It duplicates the TArray example from
// PETE (http://www.codesourcery.com/pooma/download.html)
#include <iostream>
#include <boost/mpl/int.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
using proto::_;
// This grammar describes which TArray expressions
// are allowed; namely, int and array terminals
// plus, minus, multiplies and divides of TArray expressions.
struct TArrayGrammar
  : proto::or_<
        proto::terminal< int >
      , proto::terminal< int[3] >
      , proto::plus< TArrayGrammar, TArrayGrammar >
      , proto::minus< TArrayGrammar, TArrayGrammar >
      , proto::multiplies< TArrayGrammar, TArrayGrammar >
      , proto::divides< TArrayGrammar, TArrayGrammar >
    >
{};
template<typename Expr>
struct TArrayExpr;
// Tell proto that in the TArrayDomain, all
// expressions should be wrapped in TArrayExpr<> and
// must conform to the TArrayGrammar
struct TArrayDomain
  : proto::domain<proto::generator<TArrayExpr>, TArrayGrammar>
{};
// Here is an evaluation context that indexes into a TArray
// expression, and combines the result.
struct TArraySubscriptCtx
  : proto::callable_context< TArraySubscriptCtx const >
{
    typedef int result_type;
    TArraySubscriptCtx(std::ptrdiff_t i)
      : i_(i)
    {}
    // Index array terminals with our subscript. Everything
    // else will be handled by the default evaluation context.
    int operator ()(proto::tag::terminal, int const (&data)[3]) const
    {
        return data[this->i_];
    }
    std::ptrdiff_t i_;
};
// Here is an evaluation context that prints a TArray expression.
struct TArrayPrintCtx
  : proto::callable_context< TArrayPrintCtx const >
{
    typedef std::ostream &result_type;
    TArrayPrintCtx() {}
    std::ostream &operator ()(proto::tag::terminal, int i) const
    {
        return std::cout << i;
    }
    std::ostream &operator ()(proto::tag::terminal, int const (&arr)[3]) const
    {
        return std::cout << '{' << arr[0] << ", " << arr[1] << ", " << arr[2] << '}';
    }
    template<typename L, typename R>
    std::ostream &operator ()(proto::tag::plus, L const &l, R const &r) const
    {
        return std::cout << '(' << l << " + " << r << ')';
    }
    template<typename L, typename R>
    std::ostream &operator ()(proto::tag::minus, L const &l, R const &r) const
    {
        return std::cout << '(' << l << " - " << r << ')';
    }
    template<typename L, typename R>
    std::ostream &operator ()(proto::tag::multiplies, L const &l, R const &r) const
    {
        return std::cout << l << " * " << r;
    }
    template<typename L, typename R>
    std::ostream &operator ()(proto::tag::divides, L const &l, R const &r) const
    {
        return std::cout << l << " / " << r;
    }
};
// Here is the domain-specific expression wrapper, which overrides
// operator [] to evaluate the expression using the TArraySubscriptCtx.
template<typename Expr>
struct TArrayExpr
  : proto::extends<Expr, TArrayExpr<Expr>, TArrayDomain>
{
    typedef proto::extends<Expr, TArrayExpr<Expr>, TArrayDomain> base_type;
    TArrayExpr( Expr const & expr = Expr() )
      : base_type( expr )
    {}
    // Use the TArraySubscriptCtx to implement subscripting
    // of a TArray expression tree.
    int operator []( std::ptrdiff_t i ) const
    {
        TArraySubscriptCtx const ctx(i);
        return proto::eval(*this, ctx);
    }
    // Use the TArrayPrintCtx to display a TArray expression tree.
    friend std::ostream &operator <<(std::ostream &sout, TArrayExpr<Expr> const &expr)
    {
        TArrayPrintCtx const ctx;
        return proto::eval(expr, ctx);
    }
};
// Here is our TArray terminal, implemented in terms of TArrayExpr
// It is basically just an array of 3 integers.
struct TArray
  : TArrayExpr< proto::terminal< int[3] >::type >
{
    explicit TArray( int i = 0, int j = 0, int k = 0 )
    {
        (*this)[0] = i;
        (*this)[1] = j;
        (*this)[2] = k;
    }
    // Here we override operator [] to give read/write access to
    // the elements of the array. (We could use the TArrayExpr
    // operator [] if we made the subscript context smarter about
    // returning non-const reference when appropriate.)
    int &operator [](std::ptrdiff_t i)
    {
        return proto::value(*this)[i];
    }
    int const &operator [](std::ptrdiff_t i) const
    {
        return proto::value(*this)[i];
    }
    // Here we define a operator = for TArray terminals that
    // takes a TArray expression.
    template< typename Expr >
    TArray &operator =(Expr const & expr)
    {
        // proto::as_expr<TArrayDomain>(expr) is the same as
        // expr unless expr is an integer, in which case it
        // is made into a TArrayExpr terminal first.
        return this->assign(proto::as_expr<TArrayDomain>(expr));
    }
    template< typename Expr >
    TArray &printAssign(Expr const & expr)
    {
        *this = expr;
        std::cout << *this << " = " << expr << std::endl;
        return *this;
    }
private:
    template< typename Expr >
    TArray &assign(Expr const & expr)
    {
        // expr[i] here uses TArraySubscriptCtx under the covers.
        (*this)[0] = expr[0];
        (*this)[1] = expr[1];
        (*this)[2] = expr[2];
        return *this;
    }
};
int main()
{
    TArray a(3,1,2);
    TArray b;
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    b[0] = 7; b[1] = 33; b[2] = -99;
    TArray c(a);
    std::cout << c << std::endl;
    a = 0;
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    a = b + c;
    std::cout << a << std::endl;
    a.printAssign(b+c*(b + 3*c));
    return 0;
}

Это простой пример использования<proto::extends<>>для расширения терминального типа с дополнительным поведением и использования пользовательских контекстов и<proto::eval()>для оценки выражений. Это порт примера Vec3 изPETE.

///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is a simple example using proto::extends to extend a terminal type with
// additional behaviors, and using custom contexts and proto::eval for
// evaluating expressions. It is a port of the Vec3 example
// from PETE (http://www.codesourcery.com/pooma/download.html).
#include <iostream>
#include <functional>
#include <boost/assert.hpp>
#include <boost/mpl/int.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
#include <boost/proto/proto_typeof.hpp>
#include <boost/proto/transform.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
using proto::_;
// Here is an evaluation context that indexes into a Vec3
// expression, and combines the result.
struct Vec3SubscriptCtx
  : proto::callable_context< Vec3SubscriptCtx const >
{
    typedef int result_type;
    Vec3SubscriptCtx(int i)
      : i_(i)
    {}
    // Index array terminals with our subscript. Everything
    // else will be handled by the default evaluation context.
    int operator ()(proto::tag::terminal, int const (&arr)[3]) const
    {
        return arr[this->i_];
    }
    int i_;
};
// Here is an evaluation context that counts the number
// of Vec3 terminals in an expression.
struct CountLeavesCtx
  : proto::callable_context< CountLeavesCtx, proto::null_context >
{
    CountLeavesCtx()
      : count(0)
      {}
      typedef void result_type;
      void operator ()(proto::tag::terminal, int const(&)[3])
      {
          ++this->count;
      }
      int count;
};
struct iplus : std::plus<int>, proto::callable {};
// Here is a transform that does the same thing as the above context.
// It demonstrates the use of the std::plus<> function object
// with the fold transform. With minor modifications, this
// transform could be used to calculate the leaf count at compile
// time, rather than at runtime.
struct CountLeaves
  : proto::or_<
        // match a Vec3 terminal, return 1
        proto::when<proto::terminal<int[3]>, mpl::int_<1>() >
        // match a terminal, return int() (which is 0)
      , proto::when<proto::terminal<_>, int() >
        // fold everything else, using std::plus<> to add
        // the leaf count of each child to the accumulated state.
      , proto::otherwise< proto::fold<_, int(), iplus(CountLeaves, proto::_state) > >
    >
{};
// Here is the Vec3 struct, which is a vector of 3 integers.
struct Vec3
  : proto::extends<proto::terminal<int[3]>::type, Vec3>
{
    explicit Vec3(int i=0, int j=0, int k=0)
    {
        (*this)[0] = i;
        (*this)[1] = j;
        (*this)[2] = k;
    }
    int &operator [](int i)
    {
        return proto::value(*this)[i];
    }
    int const &operator [](int i) const
    {
        return proto::value(*this)[i];
    }
    // Here we define a operator = for Vec3 terminals that
    // takes a Vec3 expression.
    template< typename Expr >
    Vec3 &operator =(Expr const & expr)
    {
        typedef Vec3SubscriptCtx const CVec3SubscriptCtx;
        (*this)[0] = proto::eval(proto::as_expr(expr), CVec3SubscriptCtx(0));
        (*this)[1] = proto::eval(proto::as_expr(expr), CVec3SubscriptCtx(1));
        (*this)[2] = proto::eval(proto::as_expr(expr), CVec3SubscriptCtx(2));
        return *this;
    }
    // This copy-assign is needed because a template is never
    // considered for copy assignment.
    Vec3 &operator=(Vec3 const &that)
    {
        (*this)[0] = that[0];
        (*this)[1] = that[1];
        (*this)[2] = that[2];
        return *this;
    }
    void print() const
    {
        std::cout << '{' << (*this)[0]
                  << ", " << (*this)[1]
                  << ", " << (*this)[2]
                  << '}' << std::endl;
    }
};
// The count_leaves() function uses the CountLeaves transform and
// to count the number of leaves in an expression.
template<typename Expr>
int count_leaves(Expr const &expr)
{
    // Count the number of Vec3 terminals using the
    // CountLeavesCtx evaluation context.
    CountLeavesCtx ctx;
    proto::eval(expr, ctx);
    // This is another way to count the leaves using a transform.
    int i = 0;
    BOOST_ASSERT( CountLeaves()(expr, i, i) == ctx.count );
    return ctx.count;
}
int main()
{
    Vec3 a, b, c;
    c = 4;
    b[0] = -1;
    b[1] = -2;
    b[2] = -3;
    a = b + c;
    a.print();
    Vec3 d;
    BOOST_PROTO_AUTO(expr1, b + c);
    d = expr1;
    d.print();
    int num = count_leaves(expr1);
    std::cout << num << std::endl;
    BOOST_PROTO_AUTO(expr2, b + 3 * c);
    num = count_leaves(expr2);
    std::cout << num << std::endl;
    BOOST_PROTO_AUTO(expr3, b + c * d);
    num = count_leaves(expr3);
    std::cout << num << std::endl;
    return 0;
}

Это пример использования<BOOST_PROTO_DEFINE_OPERATORS()>для Protofy-выражений с использованием<std::vector<>>, типа non-Proto. Это порт векторного примера изPETE.

///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is an example of using BOOST_PROTO_DEFINE_OPERATORS to Protofy
// expressions using std::vector<>, a non-proto type. It is a port of the
// Vector example from PETE (http://www.codesourcery.com/pooma/download.html).
#include <vector>
#include <iostream>
#include <stdexcept>
#include <boost/mpl/bool.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/debug.hpp>
#include <boost/proto/context.hpp>
#include <boost/utility/enable_if.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
using proto::_;
template<typename Expr>
struct VectorExpr;
// Here is an evaluation context that indexes into a std::vector
// expression and combines the result.
struct VectorSubscriptCtx
{
    VectorSubscriptCtx(std::size_t i)
      : i_(i)
    {}
    // Unless this is a vector terminal, use the
    // default evaluation context
    template<typename Expr, typename EnableIf = void>
    struct eval
      : proto::default_eval<Expr, VectorSubscriptCtx const>
    {};
    // Index vector terminals with our subscript.
    template<typename Expr>
    struct eval<
        Expr
      , typename boost::enable_if<
            proto::matches<Expr, proto::terminal<std::vector<_, _> > >
        >::type
    >
    {
        typedef typename proto::result_of::value<Expr>::type::value_type result_type;
        result_type operator ()(Expr &expr, VectorSubscriptCtx const &ctx) const
        {
            return proto::value(expr)[ctx.i_];
        }
    };
    std::size_t i_;
};
// Here is an evaluation context that verifies that all the
// vectors in an expression have the same size.
struct VectorSizeCtx
{
    VectorSizeCtx(std::size_t size)
      : size_(size)
    {}
    // Unless this is a vector terminal, use the
    // null evaluation context
    template<typename Expr, typename EnableIf = void>
    struct eval
      : proto::null_eval<Expr, VectorSizeCtx const>
    {};
    // Index array terminals with our subscript. Everything
    // else will be handled by the default evaluation context.
    template<typename Expr>
    struct eval<
        Expr
      , typename boost::enable_if<
            proto::matches<Expr, proto::terminal<std::vector<_, _> > >
        >::type
    >
    {
        typedef void result_type;
        result_type operator ()(Expr &expr, VectorSizeCtx const &ctx) const
        {
            if(ctx.size_ != proto::value(expr).size())
            {
                throw std::runtime_error("LHS and RHS are not compatible");
            }
        }
    };
    std::size_t size_;
};
// A grammar which matches all the assignment operators,
// so we can easily disable them.
struct AssignOps
  : proto::switch_<struct AssignOpsCases>
{};
// Here are the cases used by the switch_ above.
struct AssignOpsCases
{
    template<typename Tag, int D = 0> struct case_  : proto::not_<_> {};
    template<int D> struct case_< proto::tag::plus_assign, D >         : _ {};
    template<int D> struct case_< proto::tag::minus_assign, D >        : _ {};
    template<int D> struct case_< proto::tag::multiplies_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::divides_assign, D >      : _ {};
    template<int D> struct case_< proto::tag::modulus_assign, D >      : _ {};
    template<int D> struct case_< proto::tag::shift_left_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::shift_right_assign, D >  : _ {};
    template<int D> struct case_< proto::tag::bitwise_and_assign, D >  : _ {};
    template<int D> struct case_< proto::tag::bitwise_or_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::bitwise_xor_assign, D >  : _ {};
};
// A vector grammar is a terminal or some op that is not an
// assignment op. (Assignment will be handled specially.)
struct VectorGrammar
  : proto::or_<
        proto::terminal<_>
      , proto::and_<proto::nary_expr<_, proto::vararg<VectorGrammar> >, proto::not_<AssignOps> >
    >
{};
// Expressions in the vector domain will be wrapped in VectorExpr<>
// and must conform to the VectorGrammar
struct VectorDomain
  : proto::domain<proto::generator<VectorExpr>, VectorGrammar>
{};
// Here is VectorExpr, which extends a proto expr type by
// giving it an operator [] which uses the VectorSubscriptCtx
// to evaluate an expression with a given index.
template<typename Expr>
struct VectorExpr
  : proto::extends<Expr, VectorExpr<Expr>, VectorDomain>
{
    explicit VectorExpr(Expr const &expr)
      : proto::extends<Expr, VectorExpr<Expr>, VectorDomain>(expr)
    {}
    // Use the VectorSubscriptCtx to implement subscripting
    // of a Vector expression tree.
    typename proto::result_of::eval<Expr const, VectorSubscriptCtx const>::type
    operator []( std::size_t i ) const
    {
        VectorSubscriptCtx const ctx(i);
        return proto::eval(*this, ctx);
    }
};
// Define a trait type for detecting vector terminals, to
// be used by the BOOST_PROTO_DEFINE_OPERATORS macro below.
template<typename T>
struct IsVector
  : mpl::false_
{};
template<typename T, typename A>
struct IsVector<std::vector<T, A> >
  : mpl::true_
{};
namespace VectorOps
{
    // This defines all the overloads to make expressions involving
    // std::vector to build expression templates.
    BOOST_PROTO_DEFINE_OPERATORS(IsVector, VectorDomain)
    typedef VectorSubscriptCtx const CVectorSubscriptCtx;
    // Assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &assign(std::vector<T, A> &arr, Expr const &expr)
    {
        VectorSizeCtx const size(arr.size());
        proto::eval(proto::as_expr<VectorDomain>(expr), size); // will throw if the sizes don't match
        for(std::size_t i = 0; i < arr.size(); ++i)
        {
            arr[i] = proto::as_expr<VectorDomain>(expr)[i];
        }
        return arr;
    }
    // Add-assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &operator +=(std::vector<T, A> &arr, Expr const &expr)
    {
        VectorSizeCtx const size(arr.size());
        proto::eval(proto::as_expr<VectorDomain>(expr), size); // will throw if the sizes don't match
        for(std::size_t i = 0; i < arr.size(); ++i)
        {
            arr[i] += proto::as_expr<VectorDomain>(expr)[i];
        }
        return arr;
    }
}
int main()
{
    using namespace VectorOps;
    int i;
    const int n = 10;
    std::vector<int> a,b,c,d;
    std::vector<double> e(n);
    for (i = 0; i < n; ++i)
    {
        a.push_back(i);
        b.push_back(2*i);
        c.push_back(3*i);
        d.push_back(i);
    }
    VectorOps::assign(b, 2);
    VectorOps::assign(d, a + b * c);
    a += if_else(d < 30, b, c);
    VectorOps::assign(e, c);
    e += e - 4 / (c + 1);
    for (i = 0; i < n; ++i)
    {
        std::cout
            << " a(" << i << ") = " << a[i]
            << " b(" << i << ") = " << b[i]
            << " c(" << i << ") = " << c[i]
            << " d(" << i << ") = " << d[i]
            << " e(" << i << ") = " << e[i]
            << std::endl;
    }
}

Это пример использования<BOOST_PROTO_DEFINE_OPERATORS()>для выражений Protofy с использованием<std::vector<>>и<std::list<>>типов non-Proto. Это порт смешанного примера изPETE.

///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is an example of using BOOST_PROTO_DEFINE_OPERATORS to Protofy
// expressions using std::vector<> and std::list, non-proto types. It is a port
// of the Mixed example from PETE.
// (http://www.codesourcery.com/pooma/download.html).
#include <list>
#include <cmath>
#include <vector>
#include <complex>
#include <iostream>
#include <stdexcept>
#include <boost/proto/core.hpp>
#include <boost/proto/debug.hpp>
#include <boost/proto/context.hpp>
#include <boost/proto/transform.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/typeof/std/list.hpp>
#include <boost/typeof/std/vector.hpp>
#include <boost/typeof/std/complex.hpp>
#include <boost/type_traits/remove_reference.hpp>
namespace proto = boost::proto;
namespace mpl = boost::mpl;
using proto::_;
template<typename Expr>
struct MixedExpr;
template<typename Iter>
struct iterator_wrapper
{
    typedef Iter iterator;
    explicit iterator_wrapper(Iter iter)
      : it(iter)
    {}
    mutable Iter it;
};
struct begin : proto::callable
{
    template<class Sig>
    struct result;
    template<class This, class Cont>
    struct result<This(Cont)>
      : proto::result_of::as_expr<
            iterator_wrapper<typename boost::remove_reference<Cont>::type::const_iterator>
        >
    {};
    template<typename Cont>
    typename result<begin(Cont const &)>::type
    operator ()(Cont const &cont) const
    {
        iterator_wrapper<typename Cont::const_iterator> it(cont.begin());
        return proto::as_expr(it);
    }
};
// Here is a grammar that replaces vector and list terminals with their
// begin iterators
struct Begin
  : proto::or_<
        proto::when< proto::terminal< std::vector<_, _> >, begin(proto::_value) >
      , proto::when< proto::terminal< std::list<_, _> >, begin(proto::_value) >
      , proto::when< proto::terminal<_> >
      , proto::when< proto::nary_expr<_, proto::vararg<Begin> > >
    >
{};
// Here is an evaluation context that dereferences iterator
// terminals.
struct DereferenceCtx
{
    // Unless this is an iterator terminal, use the
    // default evaluation context
    template<typename Expr, typename EnableIf = void>
    struct eval
      : proto::default_eval<Expr, DereferenceCtx const>
    {};
    // Dereference iterator terminals.
    template<typename Expr>
    struct eval<
        Expr
      , typename boost::enable_if<
            proto::matches<Expr, proto::terminal<iterator_wrapper<_> > >
        >::type
    >
    {
        typedef typename proto::result_of::value<Expr>::type IteratorWrapper;
        typedef typename IteratorWrapper::iterator iterator;
        typedef typename std::iterator_traits<iterator>::reference result_type;
        result_type operator ()(Expr &expr, DereferenceCtx const &) const
        {
            return *proto::value(expr).it;
        }
    };
};
// Here is an evaluation context that increments iterator
// terminals.
struct IncrementCtx
{
    // Unless this is an iterator terminal, use the
    // default evaluation context
    template<typename Expr, typename EnableIf = void>
    struct eval
      : proto::null_eval<Expr, IncrementCtx const>
    {};
    // advance iterator terminals.
    template<typename Expr>
    struct eval<
        Expr
      , typename boost::enable_if<
            proto::matches<Expr, proto::terminal<iterator_wrapper<_> > >
        >::type
    >
    {
        typedef void result_type;
        result_type operator ()(Expr &expr, IncrementCtx const &) const
        {
            ++proto::value(expr).it;
        }
    };
};
// A grammar which matches all the assignment operators,
// so we can easily disable them.
struct AssignOps
  : proto::switch_<struct AssignOpsCases>
{};
// Here are the cases used by the switch_ above.
struct AssignOpsCases
{
    template<typename Tag, int D = 0> struct case_  : proto::not_<_> {};
    template<int D> struct case_< proto::tag::plus_assign, D >         : _ {};
    template<int D> struct case_< proto::tag::minus_assign, D >        : _ {};
    template<int D> struct case_< proto::tag::multiplies_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::divides_assign, D >      : _ {};
    template<int D> struct case_< proto::tag::modulus_assign, D >      : _ {};
    template<int D> struct case_< proto::tag::shift_left_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::shift_right_assign, D >  : _ {};
    template<int D> struct case_< proto::tag::bitwise_and_assign, D >  : _ {};
    template<int D> struct case_< proto::tag::bitwise_or_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::bitwise_xor_assign, D >  : _ {};
};
// An expression conforms to the MixedGrammar if it is a terminal or some
// op that is not an assignment op. (Assignment will be handled specially.)
struct MixedGrammar
  : proto::or_<
        proto::terminal<_>
      , proto::and_<
            proto::nary_expr<_, proto::vararg<MixedGrammar> >
          , proto::not_<AssignOps>
        >
    >
{};
// Expressions in the MixedDomain will be wrapped in MixedExpr<>
// and must conform to the MixedGrammar
struct MixedDomain
  : proto::domain<proto::generator<MixedExpr>, MixedGrammar>
{};
// Here is MixedExpr, a wrapper for expression types in the MixedDomain.
template<typename Expr>
struct MixedExpr
  : proto::extends<Expr, MixedExpr<Expr>, MixedDomain>
{
    explicit MixedExpr(Expr const &expr)
      : MixedExpr::proto_extends(expr)
    {}
private:
    // hide this:
    using proto::extends<Expr, MixedExpr<Expr>, MixedDomain>::operator [];
};
// Define a trait type for detecting vector and list terminals, to
// be used by the BOOST_PROTO_DEFINE_OPERATORS macro below.
template<typename T>
struct IsMixed
  : mpl::false_
{};
template<typename T, typename A>
struct IsMixed<std::list<T, A> >
  : mpl::true_
{};
template<typename T, typename A>
struct IsMixed<std::vector<T, A> >
  : mpl::true_
{};
namespace MixedOps
{
    // This defines all the overloads to make expressions involving
    // std::vector to build expression templates.
    BOOST_PROTO_DEFINE_OPERATORS(IsMixed, MixedDomain)
    struct assign_op
    {
        template<typename T, typename U>
        void operator ()(T &t, U const &u) const
        {
            t = u;
        }
    };
    struct plus_assign_op
    {
        template<typename T, typename U>
        void operator ()(T &t, U const &u) const
        {
            t += u;
        }
    };
    struct minus_assign_op
    {
        template<typename T, typename U>
        void operator ()(T &t, U const &u) const
        {
            t -= u;
        }
    };
    struct sin_
    {
        template<typename Sig>
        struct result;
        template<typename This, typename Arg>
        struct result<This(Arg)>
          : boost::remove_const<typename boost::remove_reference<Arg>::type>
        {};
        template<typename Arg>
        Arg operator ()(Arg const &a) const
        {
            return std::sin(a);
        }
    };
    template<typename A>
    typename proto::result_of::make_expr<
        proto::tag::function
      , MixedDomain
      , sin_ const
      , A const &
    >::type sin(A const &a)
    {
        return proto::make_expr<proto::tag::function, MixedDomain>(sin_(), boost::ref(a));
    }
    template<typename FwdIter, typename Expr, typename Op>
    void evaluate(FwdIter begin, FwdIter end, Expr const &expr, Op op)
    {
        IncrementCtx const inc = {};
        DereferenceCtx const deref = {};
        typename boost::result_of<Begin(Expr const &)>::type expr2 = Begin()(expr);
        for(; begin != end; ++begin)
        {
            op(*begin, proto::eval(expr2, deref));
            proto::eval(expr2, inc);
        }
    }
    // Add-assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &assign(std::vector<T, A> &arr, Expr const &expr)
    {
        evaluate(arr.begin(), arr.end(), proto::as_expr<MixedDomain>(expr), assign_op());
        return arr;
    }
    // Add-assign to a list from some expression.
    template<typename T, typename A, typename Expr>
    std::list<T, A> &assign(std::list<T, A> &arr, Expr const &expr)
    {
        evaluate(arr.begin(), arr.end(), proto::as_expr<MixedDomain>(expr), assign_op());
        return arr;
    }
    // Add-assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &operator +=(std::vector<T, A> &arr, Expr const &expr)
    {
        evaluate(arr.begin(), arr.end(), proto::as_expr<MixedDomain>(expr), plus_assign_op());
        return arr;
    }
    // Add-assign to a list from some expression.
    template<typename T, typename A, typename Expr>
    std::list<T, A> &operator +=(std::list<T, A> &arr, Expr const &expr)
    {
        evaluate(arr.begin(), arr.end(), proto::as_expr<MixedDomain>(expr), plus_assign_op());
        return arr;
    }
    // Minus-assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &operator -=(std::vector<T, A> &arr, Expr const &expr)
    {
        evaluate(arr.begin(), arr.end(), proto::as_expr<MixedDomain>(expr), minus_assign_op());
        return arr;
    }
    // Minus-assign to a list from some expression.
    template<typename T, typename A, typename Expr>
    std::list<T, A> &operator -=(std::list<T, A> &arr, Expr const &expr)
    {
        evaluate(arr.begin(), arr.end(), proto::as_expr<MixedDomain>(expr), minus_assign_op());
        return arr;
    }
}
int main()
{
    using namespace MixedOps;
    int n = 10;
    std::vector<int> a,b,c,d;
    std::list<double> e;
    std::list<std::complex<double> > f;
    int i;
    for(i = 0;i < n; ++i)
    {
        a.push_back(i);
        b.push_back(2*i);
        c.push_back(3*i);
        d.push_back(i);
        e.push_back(0.0);
        f.push_back(std::complex<double>(1.0, 1.0));
    }
    MixedOps::assign(b, 2);
    MixedOps::assign(d, a + b * c);
    a += if_else(d < 30, b, c);
    MixedOps::assign(e, c);
    e += e - 4 / (c + 1);
    f -= sin(0.1 * e * std::complex<double>(0.2, 1.2));
    std::list<double>::const_iterator ei = e.begin();
    std::list<std::complex<double> >::const_iterator fi = f.begin();
    for (i = 0; i < n; ++i)
    {
        std::cout
            << "a(" << i << ") = " << a[i]
            << " b(" << i << ") = " << b[i]
            << " c(" << i << ") = " << c[i]
            << " d(" << i << ") = " << d[i]
            << " e(" << i << ") = " << *ei++
            << " f(" << i << ") = " << *fi++
            << std::endl;
    }
}

Демонстрация того, как реализовать<map_list_of()>от Роста. Назначение библиотеки с помощью Proto.<map_list_assign()>используется для удобной инициализации<std::map<>>. Используя Proto, мы можем избежать любого динамического распределения при построении промежуточного представления.

//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is a port of map_list_of() from the Boost.Assign library.
// It has the advantage of being more efficient at runtime by not
// building any temporary container that requires dynamic allocation.
#include <map>
#include <string>
#include <iostream>
#include <boost/proto/core.hpp>
#include <boost/proto/transform.hpp>
#include <boost/type_traits/add_reference.hpp>
namespace proto = boost::proto;
using proto::_;
struct map_list_of_tag
{};
// A simple callable function object that inserts a
// (key,value) pair into a map.
struct insert
  : proto::callable
{
    template<typename Sig>
    struct result;
    template<typename This, typename Map, typename Key, typename Value>
    struct result<This(Map, Key, Value)>
      : boost::add_reference<Map>
    {};
    template<typename Map, typename Key, typename Value>
    Map &operator()(Map &map, Key const &key, Value const &value) const
    {
        map.insert(typename Map::value_type(key, value));
        return map;
    }
};
// Work-arounds for Microsoft Visual C++ 7.1
#if BOOST_WORKAROUND(BOOST_MSVC, == 1310)
#define MapListOf(x) proto::call<MapListOf(x)>
#define _value(x) call<proto::_value(x)>
#endif
// The grammar for valid map-list expressions, and a
// transform that populates the map.
struct MapListOf
  : proto::or_<
        proto::when<
            // map_list_of(a,b)
            proto::function<
                proto::terminal<map_list_of_tag>
              , proto::terminal<_>
              , proto::terminal<_>
            >
          , insert(
                proto::_data
              , proto::_value(proto::_child1)
              , proto::_value(proto::_child2)
            )
        >
      , proto::when<
            // map_list_of(a,b)(c,d)...
            proto::function<
                MapListOf
              , proto::terminal<_>
              , proto::terminal<_>
            >
          , insert(
                MapListOf(proto::_child0)
              , proto::_value(proto::_child1)
              , proto::_value(proto::_child2)
            )
        >
    >
{};
#if BOOST_WORKAROUND(BOOST_MSVC, == 1310)
#undef MapListOf
#undef _value
#endif
template<typename Expr>
struct map_list_of_expr;
struct map_list_of_dom
  : proto::domain<proto::pod_generator<map_list_of_expr>, MapListOf>
{};
// An expression wrapper that provides a conversion to a
// map that uses the MapListOf
template<typename Expr>
struct map_list_of_expr
{
    BOOST_PROTO_BASIC_EXTENDS(Expr, map_list_of_expr, map_list_of_dom)
    BOOST_PROTO_EXTENDS_FUNCTION()
    template<typename Key, typename Value, typename Cmp, typename Al>
    operator std::map<Key, Value, Cmp, Al> () const
    {
        BOOST_MPL_ASSERT((proto::matches<Expr, MapListOf>));
        std::map<Key, Value, Cmp, Al> map;
        return MapListOf()(*this, 0, map);
    }
};
map_list_of_expr<proto::terminal<map_list_of_tag>::type> const map_list_of = {{{}}};
int main()
{
    // Initialize a map:
    std::map<std::string, int> op =
        map_list_of
            ("<", 1)
            ("<=",2)
            (">", 3)
            (">=",4)
            ("=", 5)
            ("<>",6)
        ;
    std::cout << "\"<\"  --> " << op["<"] << std::endl;
    std::cout << "\"<=\" --> " << op["<="] << std::endl;
    std::cout << "\">\"  --> " << op[">"] << std::endl;
    std::cout << "\">=\" --> " << op[">="] << std::endl;
    std::cout << "\"=\"  --> " << op["="] << std::endl;
    std::cout << "\"<>\" --> " << op["<>"] << std::endl;
    return 0;
}

Продвинутый пример преобразования Прото, реализующего конструкцию Говарда Хиннана длябудущих групп, которые блокируют все или некоторые асинхронные операции для завершения и возвращают их результаты в набор соответствующего типа.

//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is an example of using Proto transforms to implement
// Howard Hinnant's future group proposal.
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include <boost/fusion/include/joint_view.hpp>
#include <boost/fusion/include/single_view.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/transform.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
namespace fusion = boost::fusion;
using proto::_;
template<class L,class R>
struct pick_left
{
    BOOST_MPL_ASSERT((boost::is_same<L, R>));
    typedef L type;
};
// Work-arounds for Microsoft Visual C++ 7.1
#if BOOST_WORKAROUND(BOOST_MSVC, == 1310)
#define FutureGroup(x) proto::call<FutureGroup(x)>
#endif
// Define the grammar of future group expression, as well as a
// transform to turn them into a Fusion sequence of the correct
// type.
struct FutureGroup
  : proto::or_<
        // terminals become a single-element Fusion sequence
        proto::when<
            proto::terminal<_>
          , fusion::single_view<proto::_value>(proto::_value)
        >
        // (a && b) becomes a concatenation of the sequence
        // from 'a' and the one from 'b':
      , proto::when<
            proto::logical_and<FutureGroup, FutureGroup>
          , fusion::joint_view<
                boost::add_const<FutureGroup(proto::_left) >
              , boost::add_const<FutureGroup(proto::_right) >
            >(FutureGroup(proto::_left), FutureGroup(proto::_right))
        >
        // (a || b) becomes the sequence for 'a', so long
        // as it is the same as the sequence for 'b'.
      , proto::when<
            proto::logical_or<FutureGroup, FutureGroup>
          , pick_left<
                FutureGroup(proto::_left)
              , FutureGroup(proto::_right)
            >(FutureGroup(proto::_left))
        >
    >
{};
#if BOOST_WORKAROUND(BOOST_MSVC, == 1310)
#undef FutureGroup
#endif
template<class E>
struct future_expr;
struct future_dom
  : proto::domain<proto::generator<future_expr>, FutureGroup>
{};
// Expressions in the future group domain have a .get()
// member function that (ostensibly) blocks for the futures
// to complete and returns the results in an appropriate
// tuple.
template<class E>
struct future_expr
  : proto::extends<E, future_expr<E>, future_dom>
{
    explicit future_expr(E const &e)
      : future_expr::proto_extends(e)
    {}
    typename fusion::result_of::as_vector<
        typename boost::result_of<FutureGroup(E)>::type
    >::type
    get() const
    {
        return fusion::as_vector(FutureGroup()(*this));
    }
};
// The future<> type has an even simpler .get()
// member function.
template<class T>
struct future
  : future_expr<typename proto::terminal<T>::type>
{
    future(T const &t = T())
      : future::proto_derived_expr(future::proto_base_expr::make(t))
    {}
    T get() const
    {
        return proto::value(*this);
    }
};
// TEST CASES
struct A {};
struct B {};
struct C {};
int main()
{
    using fusion::vector;
    future<A> a;
    future<B> b;
    future<C> c;
    future<vector<A,B> > ab;
    // Verify that various future groups have the
    // correct return types.
    A                       t0 = a.get();
    vector<A, B, C>         t1 = (a && b && c).get();
    vector<A, C>            t2 = ((a || a) && c).get();
    vector<A, B, C>         t3 = ((a && b || a && b) && c).get();
    vector<vector<A, B>, C> t4 = ((ab || ab) && c).get();
    return 0;
}

Это продвинутый пример, который показывает, как реализовать простую лямбду EDSL с Proto, как Boost. Lambda_library. Он использует контексты, преобразования и расширение выражения.

///////////////////////////////////////////////////////////////////////////////
// Copyright 2008 Eric Niebler. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This example builds a simple but functional lambda library using Proto.
#include <iostream>
#include <algorithm>
#include <boost/mpl/int.hpp>
#include <boost/mpl/min_max.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/next_prior.hpp>
#include <boost/fusion/tuple.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/typeof/std/ostream.hpp>
#include <boost/typeof/std/iostream.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
#include <boost/proto/transform.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
namespace fusion = boost::fusion;
using proto::_;
// Forward declaration of the lambda expression wrapper
template<typename T>
struct lambda;
struct lambda_domain
  : proto::domain<proto::pod_generator<lambda> >
{};
template<typename I>
struct placeholder
{
    typedef I arity;
};
template<typename T>
struct placeholder_arity
{
    typedef typename T::arity type;
};
// The lambda grammar, with the transforms for calculating the max arity
struct lambda_arity
  : proto::or_<
        proto::when<
            proto::terminal< placeholder<_> >
          , mpl::next<placeholder_arity<proto::_value> >()
        >
      , proto::when< proto::terminal<_>
          , mpl::int_<0>()
        >
      , proto::when<
            proto::nary_expr<_, proto::vararg<_> >
          , proto::fold<_, mpl::int_<0>(), mpl::max<lambda_arity, proto::_state>()>
        >
    >
{};
// The lambda context is the same as the default context
// with the addition of special handling for lambda placeholders
template<typename Tuple>
struct lambda_context
  : proto::callable_context<lambda_context<Tuple> const>
{
    lambda_context(Tuple const &args)
      : args_(args)
    {}
    template<typename Sig>
    struct result;
    template<typename This, typename I>
    struct result<This(proto::tag::terminal, placeholder<I> const &)>
      : fusion::result_of::at<Tuple, I>
    {};
    template<typename I>
    typename fusion::result_of::at<Tuple, I>::type
    operator ()(proto::tag::terminal, placeholder<I> const &) const
    {
        return fusion::at<I>(this->args_);
    }
    Tuple args_;
};
// The lambda<> expression wrapper makes expressions polymorphic
// function objects
template<typename T>
struct lambda
{
    BOOST_PROTO_BASIC_EXTENDS(T, lambda<T>, lambda_domain)
    BOOST_PROTO_EXTENDS_ASSIGN()
    BOOST_PROTO_EXTENDS_SUBSCRIPT()
    // Calculate the arity of this lambda expression
    static int const arity = boost::result_of<lambda_arity(T)>::type::value;
    template<typename Sig>
    struct result;
    // Define nested result<> specializations to calculate the return
    // type of this lambda expression. But be careful not to evaluate
    // the return type of the nullary function unless we have a nullary
    // lambda!
    template<typename This>
    struct result<This()>
      : mpl::eval_if_c<
            0 == arity
          , proto::result_of::eval<T const, lambda_context<fusion::tuple<> > >
          , mpl::identity<void>
        >
    {};
    template<typename This, typename A0>
    struct result<This(A0)>
      : proto::result_of::eval<T const, lambda_context<fusion::tuple<A0> > >
    {};
    template<typename This, typename A0, typename A1>
    struct result<This(A0, A1)>
      : proto::result_of::eval<T const, lambda_context<fusion::tuple<A0, A1> > >
    {};
    // Define our operator () that evaluates the lambda expression.
    typename result<lambda()>::type
    operator ()() const
    {
        fusion::tuple<> args;
        lambda_context<fusion::tuple<> > ctx(args);
        return proto::eval(*this, ctx);
    }
    template<typename A0>
    typename result<lambda(A0 const &)>::type
    operator ()(A0 const &a0) const
    {
        fusion::tuple<A0 const &> args(a0);
        lambda_context<fusion::tuple<A0 const &> > ctx(args);
        return proto::eval(*this, ctx);
    }
    template<typename A0, typename A1>
    typename result<lambda(A0 const &, A1 const &)>::type
    operator ()(A0 const &a0, A1 const &a1) const
    {
        fusion::tuple<A0 const &, A1 const &> args(a0, a1);
        lambda_context<fusion::tuple<A0 const &, A1 const &> > ctx(args);
        return proto::eval(*this, ctx);
    }
};
// Define some lambda placeholders
lambda<proto::terminal<placeholder<mpl::int_<0> > >::type> const _1 = {{}};
lambda<proto::terminal<placeholder<mpl::int_<1> > >::type> const _2 = {{}};
template<typename T>
lambda<typename proto::terminal<T>::type> const val(T const &t)
{
    lambda<typename proto::terminal<T>::type> that = {{t}};
    return that;
}
template<typename T>
lambda<typename proto::terminal<T &>::type> const var(T &t)
{
    lambda<typename proto::terminal<T &>::type> that = {{t}};
    return that;
}
template<typename T>
struct construct_helper
{
    typedef T result_type; // for TR1 result_of
    T operator()() const
    { return T(); }
    // Generate BOOST_PROTO_MAX_ARITY overloads of the
    // following function call operator.
#define BOOST_PROTO_LOCAL_MACRO(N, typename_A, A_const_ref, A_const_ref_a, a)\
    template<typename_A(N)>                                       \
    T operator()(A_const_ref_a(N)) const                          \
    { return T(a(N)); }
#define BOOST_PROTO_LOCAL_a BOOST_PROTO_a
#include BOOST_PROTO_LOCAL_ITERATE()
};
// Generate BOOST_PROTO_MAX_ARITY-1 overloads of the
// following construct() function template.
#define M0(N, typename_A, A_const_ref, A_const_ref_a, ref_a)      \
template<typename T, typename_A(N)>                               \
typename proto::result_of::make_expr<                             \
    proto::tag::function                                          \
  , lambda_domain                                                 \
  , construct_helper<T>                                           \
  , A_const_ref(N)                                                \
>::type const                                                     \
construct(A_const_ref_a(N))                                       \
{                                                                 \
    return proto::make_expr<                                      \
        proto::tag::function                                      \
      , lambda_domain                                             \
    >(                                                            \
        construct_helper<T>()                                     \
      , ref_a(N)                                                  \
    );                                                            \
}
BOOST_PROTO_REPEAT_FROM_TO(1, BOOST_PROTO_MAX_ARITY, M0)
#undef M0
struct S
{
    S() {}
    S(int i, char c)
    {
        std::cout << "S(" << i << "," << c << ")\n";
    }
};
int main()
{
    // Create some lambda objects and immediately
    // invoke them by applying their operator():
    int i = ( (_1 + 2) / 4 )(42);
    std::cout << i << std::endl; // prints 11
    int j = ( (-(_1 + 2)) / 4 )(42);
    std::cout << j << std::endl; // prints -11
    double d = ( (4 - _2) * 3 )(42, 3.14);
    std::cout << d << std::endl; // prints 2.58
    // check non-const ref terminals
    (std::cout << _1 << " -- " << _2 << '\n')(42, "Life, the Universe and Everything!");
    // prints "42 -- Life, the Universe and Everything!"
    // "Nullary" lambdas work too
    int k = (val(1) + val(2))();
    std::cout << k << std::endl; // prints 3
    // check array indexing for kicks
    int integers[5] = {0};
    (var(integers)[2] = 2)();
    (var(integers)[_1] = _1)(3);
    std::cout << integers[2] << std::endl; // prints 2
    std::cout << integers[3] << std::endl; // prints 3
    // Now use a lambda with an STL algorithm!
    int rgi[4] = {1,2,3,4};
    char rgc[4] = {'a','b','c','d'};
    S rgs[4];
    std::transform(rgi, rgi+4, rgc, rgs, construct<S>(_1, _2));
    return 0;
}

Это продвинутый пример, который показывает, как внешне параметризировать преобразования грамматики. Он определяет калькулятор EDSL с грамматикой, которая может выполнять либо проверенную, либо непроверенную арифметику.

//  Copyright 2011 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is an example of how to specify a transform externally so
// that a single grammar can be used to drive multiple differnt
// calculations. In particular, it defines a calculator grammar
// that computes the result of an expression with either checked
// or non-checked division.
#include <iostream>
#include <boost/assert.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/next.hpp>
#include <boost/mpl/min_max.hpp>
#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/container/generation/make_vector.hpp>
#include <boost/proto/proto.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
namespace fusion = boost::fusion;
// The argument placeholder type
template<typename I> struct placeholder : I {};
// Give each rule in the grammar a "name". This is so that we
// can easily dispatch on it later.
struct calc_grammar;
struct divides_rule : proto::divides<calc_grammar, calc_grammar> {};
// Use external transforms in calc_gramar
struct calc_grammar
  : proto::or_<
        proto::when<
            proto::terminal<placeholder<proto::_> >
            , proto::functional::at(proto::_state, proto::_value)
        >
      , proto::when<
            proto::terminal<proto::convertible_to<double> >
          , proto::_value
        >
      , proto::when<
            proto::plus<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
      , proto::when<
            proto::minus<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
      , proto::when<
            proto::multiplies<calc_grammar, calc_grammar>
          , proto::_default<calc_grammar>
        >
        // Note that we don't specify how division nodes are
        // handled here. Proto::external_transform is a placeholder
        // for an actual transform.
      , proto::when<
            divides_rule
          , proto::external_transform
        >
    >
{};
template<typename E> struct calc_expr;
struct calc_domain : proto::domain<proto::generator<calc_expr> > {};
template<typename E>
struct calc_expr
  : proto::extends<E, calc_expr<E>, calc_domain>
{
    calc_expr(E const &e = E()) : calc_expr::proto_extends(e) {}
};
calc_expr<proto::terminal<placeholder<mpl::int_<0> > >::type> _1;
calc_expr<proto::terminal<placeholder<mpl::int_<1> > >::type> _2;
// Use proto::external_transforms to map from named grammar rules to
// transforms.
struct non_checked_division
  : proto::external_transforms<
        proto::when< divides_rule, proto::_default<calc_grammar> >
    >
{};
struct division_by_zero : std::exception {};
struct do_checked_divide
  : proto::callable
{
    typedef int result_type;
    int operator()(int left, int right) const
    {
        if (right == 0) throw division_by_zero();
        return left / right;
    }
};
// Use proto::external_transforms again, this time to map the divides_rule
// to a transforms that performs checked division.
struct checked_division
  : proto::external_transforms<
        proto::when<
            divides_rule
          , do_checked_divide(calc_grammar(proto::_left), calc_grammar(proto::_right))
        >
    >
{};
int main()
{
    non_checked_division non_checked;
    int result2 = calc_grammar()(_1 / _2, fusion::make_vector(6, 2), non_checked);
    BOOST_ASSERT(result2 == 3);
    try
    {
        checked_division checked;
        // This should throw
        int result3 = calc_grammar()(_1 / _2, fusion::make_vector(6, 0), checked);
        BOOST_ASSERT(false); // shouldn't get here!
    }
    catch(division_by_zero)
    {
        std::cout << "caught division by zero!\n";
    }
}

Proto был первоначально разработан как частьBoost.Xpressiveдля упрощения работы по преобразованию шаблона выражения в исполняемую машину конечного состояния, способную соответствовать обычному выражению. С тех пор Proto нашел применение в обновленном и улучшенном Spirit-2 и связанной с ним библиотеке кармы. В результате этих усилий Proto превратилась в общую и абстрактную грамматику и структуру преобразования деревьев, применимую в самых разных сценариях EDSL.

Грамматика и структура преобразования дерева смоделированы на основе грамматики и семантического действия Духа. Структура данных дерева экспрессии во многих отношениях аналогична структурам данных Fusion и совместима с итераторами и алгоритмами Fusion.

Синтаксис для грамматических характеристик соответствия<proto::matches<>>вдохновлен лямбда-выражениями MPL.

Идея использования типов функций для композитных преобразований Прото вдохновлена нотацией Алексея Гуртового«круглый» лямбда.

References

Ren, D. and Erwig, M. 2006. A generic recursion toolbox for Haskell or: scrap your boilerplate systematically. In Proceedings of the 2006 ACM SIGPLAN Workshop on Haskell (Portland, Oregon, USA, September 17 - 17, 2006). Haskell '06. ACM, New York, NY, 13-24. DOI=http://doi.acm.org/10.1145/1159842.1159845

Further Reading

Техническая статья о более ранней версии Proto была принята на симпозиум ACM SIGPLAN Symposium on Library-Centric Software Design LCSD'07. http://lcsd.cs.tamu.edu/2007/final/1/1_Paper.pdf. Дерево, описанное в этой статье, отличается от того, что существует сегодня.

callable transform

Преобразование формы<R(A0,A1,...)>(т.е. типа функции), где<proto::is_callable<R>::value>является<true>.<R>рассматривается как полиморфный объект функции, а аргументы рассматриваются как преобразования, которые приводят аргументы к объекту функции.

context

В Прото терминконтекстотносится к объекту, который может быть передан вместе с выражением для оценки функции<proto::eval()>. Контекст определяет, как оценивается выражение. Все контекстные структуры определяют вложенный<eval<>>шаблон, который, будучи инстанцирован с типом тега узла (например,<proto::tag::plus>), является бинарным полиморфным функциональным объектом, который принимает выражение этого типа и контекстного объекта. Таким образом, контексты связывают поведение с узлами экспрессии.

domain

В Прото терминдоменотносится к типу, который связывает выражения внутри этого домена с генераторомдля этого домена и необязательно грамматикойдля домена. Домены используются в основном для того, чтобы наполнить выражения в этом домене дополнительными членами и ограничить перегрузки оператора Proto, так что выражения, не соответствующие грамматике домена, никогда не создаются. Домены — это пустые структуры, которые наследуют от<proto::domain<>>.

domain-specific language

Язык программирования, который нацелен на конкретное проблемное пространство, предоставляя идиомы программирования, абстракции и конструкции, которые соответствуют конструкциям в этом проблемном пространстве.

embedded domain-specific language

Язык домена, реализованный как библиотека. Язык, на котором написана библиотека, называется «принимающим» языком, а язык, реализуемый библиотекой, называется «встроенным» языком.

expression

В Прото выражениеявляется гетерогенным деревом, где каждый узел представляет собой либо инстанциацию<boost::proto::expr<>>,<boost::proto::basic_expr<>>, либо некоторый тип, который является расширением (через<boost::proto::extends<>>или<BOOST_PROTO_EXTENDS()>) такой инстанциации.

expression template

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

generator

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

grammar

В Прото грамматикаявляется типом, который описывает подмножество типов экспрессии Прото. Выражения в области должны соответствовать грамматике этой области. Метафункция<proto::matches<>>оценивает, соответствует ли тип выражения грамматике. Грамматики являются либо примитивными, такими как<proto::_>, композитами, такими как<proto::plus<>>, структурами управления, такими как<proto::or_<>>, либо некоторым типом, полученным из грамматики.

object transform

Преобразование формы<R(A0,A1,...)>(т.е. типа функции), где<proto::is_callable<R>::value>является<false>.<R>рассматривается как тип объекта для построения, а аргументы рассматриваются как преобразования, которые дают параметры конструктору.

polymorphic function object

Пример типа класса с перегруженным оператором вызова функции и вложенным<result_type>типдефом или<result<>>шаблоном для расчета типа возврата оператора вызова функции.

primitive transform

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

sub-domain

Субдомен - это домен, который объявляет другой домен своим супердоменом. Выражения в поддоменах могут быть объединены с выражениями в супердомене, и полученное выражение находится в супердомене.

transform

Трансформаторы используются для манипулирования деревьями экспрессии. Они бывают трех видов: примитивные преобразования, так называемые преобразования или преобразования объектов. Преобразование<T>может быть выполнено в тройной полиморфный функциональный объект с<proto::when<>>, как в<proto::when<proto::_,T>>. Такой объект функции принимаетвыражение,состояниеиданныепараметры и вычисляет из них результат.



<#include <boost/proto/core.hpp>>Не всегда можно удержать что-то по стоимости. По умолчанию<proto::as_expr()>делает исключение для функций, абстрактных типов и iostreams (типы, полученные из<std::ios_base>). Эти объекты удерживаются путем ссылки. Все остальные удерживаются ценностью, даже массивами.

<boost::proto>Это сообщение об ошибке было создано с помощью Microsoft Visual C++. 9.0. Различные компиляторы будут излучать различные сообщения с различной степенью читаемости.


PrevUpHomeNext

Статья Users' Guide раздела 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 16:49:13/0.31201195716858/0