front_end.qbk
来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 554 行 · 第 1/3 页
QBK
554 行
typename proto::function< typename proto::terminal<pow_fun<Exp> >::type , Arg const & >::type result_type; result_type result = {{{}}, arg}; return result; }In the code above, notice how the `proto::function<>` and _terminal_ metafunctions are used to calculate the return type: `pow()` returns an expression template representing a function call where the first child is the function to call and the second is the argument to the function. (Unfortunately, the same type calculation is repeated in the body of the function so that we can initialize a local variable of the correct type. We'll see in a moment how to avoid that.)[note As with `proto::function<>`, there are metafunctions corresponding to all of the overloadable C++ operators for calculation expression types.]With the above definition of the `pow()` function, we can create calculator expressions like the one below and evaluate them using the `calculator_context` we implemented in the Introduction. // Initialize a calculator context calculator_context ctx; ctx.args.push_back(3); // let _1 be 3 // Create a calculator expression that takes one argument, // adds one to it, and raises it to the 2nd power; and then // immediately evaluate it using the calculator_context. assert( 16 == proto::eval( pow<2>( _1 + 1 ), ctx ) );[/=========================================][heading Protofying Lazy Function Arguments][/=========================================]Above, we defined a `pow()` function template that returns an expression template representing a lazy function invocation. But if we tried to call it as below, we'll run into a problem. // ERROR: pow() as defined above doesn't work when // called with a non-Proto argument. pow< 2 >( 4 );Proto expressions can only have other Proto expressions as children. But if we look at `pow()`'s function signature, we can see that if we pass it a non-Proto object, it will try to make it a child. template<int Exp, typename Arg> typename proto::function< typename proto::terminal<pow_fun<Exp> >::type , Arg const & // <=== ERROR! This may not be a Proto type! >::type pow(Arg const &arg)What we want is a way to make `Arg` into a Proto terminal if it is not a Proto expression already, and leave it alone if it is. For that, we can use _as_child_. The following implementation of the `pow()` function handles all argument types, expression templates or otherwise. // Define a lazy pow() function for the calculator DSEL. Use // proto::as_child() to Protofy the argument, but only if it // is not a Proto expression type to begin with! template<int Exp, typename Arg> typename proto::function< typename proto::terminal<pow_fun<Exp> >::type , typename proto::result_of::as_child<Arg const>::type >::type pow(Arg const &arg) { typedef typename proto::function< typename proto::terminal<pow_fun<Exp> >::type , typename proto::result_of::as_child<Arg const>::type >::type result_type; result_type result = {{{}}, proto::as_child(arg)}; return result; }Notice how we use the `proto::result_of::as_child<>` metafunction to calculate the return type, and the `proto::as_child()` function to actually normalize the argument.[/=====================================================][heading Lazy Functions Made Simple With [^make_expr()]][/=====================================================]The versions of the `pow()` function we've seen above are rather verbose. In the return type calculation, you have to be very explicit about wrapping non-Proto types. Worse, you have to restate the return type calculation in the body of `pow()` itself. Proto provides a helper for building expression templates directly that handles these mundane details for you. It's called _make_expr_. We can redefine `pow()` with it as below. // Define a lazy pow() function for the calculator DSEL. // 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 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) ); }There are some things to notice about the above code. We use `proto::result_of::make_expr<>` to calculate the return type. The first template parameter is the tag type for the expression node we're building -- in this case, `proto::tag::function`, which is the tag type Proto uses for function call expressions.Subsequent template parameters to `proto::result_of::make_expr<>` represent children nodes. If a child type is not already a Proto expression, it is made into a terminal with _as_child_. A type such as `pow_fun<Exp>` results in terminal that is held by value, whereas a type like `Arg const &` (note the reference) indicates that the result should be held by reference.In the function body is the runtime invocation of _make_expr_. It closely mirrors the return type calculation. _make_expr_ requires you to specify the node's tag type as a template parameter. The arguments to the function become the node's children. When a child should be stored by value, nothing special needs to be done. When a child should be stored by reference, you must use the `boost::ref()` function to wrap the argument. Without this extra information, the _make_expr_ function couldn't know whether to store a child by value or by reference.[endsect][/==============================================][section Adding Members by Extending Expressions][/==============================================]In this section, we'll see how to associate Proto expressions with a /domain/, how to add members to expressions within a domain, and how to control which operators are overloaded in a domain.[/==============][section Domains][/==============]In the [link boost_proto.users_guide.getting_started.hello_calculator Hello Calculator] section, we looked into making calculator expressions directly usable as lambda expressions in calls to STL algorithms, as below: double data[] = {1., 2., 3., 4.}; // Use the calculator DSEL to square each element ... HOW? std::transform( data, data + 4, data, _1 * _1 );The difficulty, if you recall, was that by default Proto expressions don't have interesting behaviors of their own. They're just trees. In particular, the expression `_1 * _1` won't have an `operator()` that takes a double and returns a double like `std::transform()` expects -- unless we give it one. To make this work, we needed to define an expression wrapper type that defined the `operator()` member function, and we needed to associate the wrapper with the calculator /domain/.In Proto, the term /domain/ refers to a type that associates expressions in that domain to an expression /generator/. The generator is just a function object that accepts an expression and does something to it, like wrapping it in an expression wrapper.You can also use a domain to associate expressions with a grammar. When you specify a domain's grammar, Proto ensures that all the expressions it generates in that domain conform to the domain's grammar. It does that by disabling any operator overloads that would create invalid expressions.[endsect][/==================================================][section:extends The [^extends<>] Expression Wrapper][/==================================================]The first step to giving your calculator expressions extra behaviors is to define a calculator domain. All expressions within the calculator domain will be imbued with calculator-ness, as we'll see. // A type to be used as a domain tag (to be defined below) struct calculator_domain;We use this domain type when extending the _expr_ type, which we do with the _extends_ class template. Here is our expression wrapper, which imbues an expression with calculator-ness. It is described below. // 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= using base_type::operator =; 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 } };We want calculator expressions to be function objects, so we have to define an `operator()` that takes and returns doubles. The `calculator<>` wrapper above does that with the help of the _extends_ template. The first template to _extends_ parameter is the expression type we are extending. The second is the type of the wrapped expression. The third parameter is the domain that this wrapper is associated with. A wrapper type like `calculator<>` that inherits from _extends_ behaves just like the expression type it has extended, with any additional behaviors you choose to give it.Although not strictly necessary in this case, we bring `extends<>::operator=` into scope with a `using` declaration. This is really only necessary if you want expressions like `_1 = 3` to create a lazily evaluated assignment. _extends_ defines the appropriate `operator=` for you, but the compiler-generated `calculator<>::operator=` will hide it unless you make it available with the `using` declaration.Note that in the implementation of `calculator<>::operator()`, we evaluate the expression with the `calculator_context` we defined earlier. As we saw before, the context is what gives the operators their meaning. In the case of the calculator, the context is also what defines the meaning of the placeholder terminals.Now that we have defined the `calculator<>` expression wrapper, we need to wrap the placeholders to imbue them with calculator-ness: calculator< proto::terminal< placeholder<0> >::type > const _1; calculator< proto::terminal< placeholder<1> >::type > const _2;[/=======================================================][heading Retaining POD-ness with [^BOOST_PROTO_EXTENDS()]]
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?