intermediate_form.qbk
来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 1,171 行 · 第 1/3 页
QBK
1,171 行
terminals that are convertible to `double`. For that, Proto provides the_convertible_to_ template. You can use it as:`proto::terminal< proto::convertible_to< double > >`.There is one more way you can perform a fuzzy match on terminals. Consider theproblem of trying to match a `std::complex<>` terminal. You can easily matcha `std::complex<float>` or a `std::complex<double>`, but how would you matchany instantiation of `std::complex<>`? You can use `proto::_` here to solvethis problem. Here is the grammar to match any `std::complex<>` instantiation: struct StdComplex : proto::terminal< std::complex< proto::_ > > {};When given a grammar like this, Proto will deconstruct the grammar and theterminal it is being matched against and see if it can match all theconstituents.[endsect][/====================================================][section:if_and_not [^if_<>], [^and_<>], and [^not_<>]][/====================================================]We've already seen how to use expression generators like `proto::terminal<>` and`proto::shift_right<>` as grammars. We've also seen _or_, which we can use toexpress a set of alternate grammars. There are a few others of interest; inparticular, _if_, _and_ and _not_.The _not_ template is the simplest. It takes a grammar as a template parameterand logically negates it; `not_<Grammar>` will match any expression that`Grammar` does /not/ match.The _if_ template is used together with a Proto transform that is evaluatedagainst expression types to find matches. (Proto transforms will be describedlater.)The _and_ template is like _or_, except that each argument of the _and_ mustmatch in order for the _and_ to match. As an example, consider the definitionof `CharString` above that uses _exact_. It could have been written without_exact_ as follows: struct CharString : proto::and_< proto::terminal< proto::_ > , proto::if_< boost::is_same< proto::_value, char const * >() > > {};This says that a `CharString` must be a terminal, /and/ its value type must bethe same as `char const *`. Notice the template argument of _if_:`boost::is_same< proto::_value, char const * >()`. This is Proto transform that compares the value type of a terminal to `char const *`.The _if_ template has a couple of variants. In addition to `if_<Condition>` youcan also say `if_<Condition, ThenGrammar>` and`if_<Condition, ThenGrammar, ElseGrammar>`. These let you select one sub-grammaror another based on the `Condition`.[endsect][/=======================================================][section:switch Improving Compile Times With [^switch_<>]][/=======================================================]When your Proto grammar gets large, you'll start to run into some scalability problems with _or_, the construct you use to specify alternate sub-grammars. First, due to limitations in C++, _or_ can only accept up to a certain number of sub-grammars, controlled by the `BOOST_PROTO_MAX_LOGICAL_ARITY` macro. This macro defaults to eight, and you can set it higher, but doing so will aggravate another scalability problem: long compile times. With _or_, alternate sub-grammars are tried in order -- like a series of cascading `if`'s -- leading to lots of unnecessary template instantiations. What you would prefer instead is something like `switch` that avoids the expense of cascading `if`'s. That's the purpose of _switch_; although less convenient than _or_, it improves compile times for larger grammars and does not have an arbitrary fixed limit on the number of sub-grammars. Let's illustrate how to use _switch_ by first writing a big grammar with _or_ and then translating it to an equivalent grammar using _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> > > {};The above might be the grammar to a more elaborate calculator DSEL. Notice that since there are more than eight sub-grammars, we had to chain the sub-grammars with a nested _or_ -- not very nice.The idea behind _switch_ is to dispatch based on an expression's tag type to a sub-grammar that handles expressions of that type. To use _switch_, you define a struct with a nested `case_<>` template, specialized on tag types. The above grammar can be expressed using _switch_ as follows. It is described below. // 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> {};Matching an expression type `E` against `proto::switch_<C>` is equivalent to matching it against `C::case_<E::proto_tag>`. By dispatching on the expression's tag type, we can jump to the sub-grammar that handles expressions of that type, skipping over all the other sub-grammars that couldn't possibly match. If there is no specialization of `case_<>` for a particular tag type, we select the primary template. In this case, the primary template inherits from `proto::not_<_>` which matches no expressions.Notice the specialization that handles terminals: // Terminal expressions are handled here template<> struct ABigGrammarCases::case_<proto::tag::terminal> : proto::or_< proto::terminal<int> , proto::terminal<double> > {};The `proto::tag::terminal` type by itself isn't enough to select an appropriate sub-grammar, so we use _or_ to list the alternate sub-grammars that match terminals.[note You might be tempted to define your `case_<>` specializations /in situ/ as follows:`` struct ABigGrammarCases { template<typename Tag> struct case_ : proto::not_<_> {}; // ERROR: not legal C++ template<> struct case_<proto::tag::terminal> /* ... */ };``Unfortunately, for arcane reasons, it is not legal to define an explicit nested specialization /in situ/ like this. It is, however, perfectly legal to define /partial/ specializations /in situ/, so you can add a extra dummy template parameter that has a default, as follows:`` 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> /* ... */ };``You might find this cleaner than defining explicit `case_<>` specializations outside of their enclosing struct.][endsect][/==================================][section Matching Vararg Expressions][/==================================]Not all of C++'s overloadable operators are unary or binary. There is theoddball `operator()` -- the function call operator -- which can have any numberof arguments. Likewise, with Proto you may define your own "operators" thatcould also take more that two arguments. As a result, there may be nodes inyour Proto expression tree that have an arbitrary number of children (up to`BOOST_PROTO_MAX_ARITY`, which is configurable). How do you write a grammar to match such a node?For such cases, Proto provides the _vararg_ class template. Its templateargument is a grammar, and the _vararg_ will match the grammar zero or moretimes. Consider a Proto lazy function called `fun()` that can take zero ormore characters as arguments, as follows: struct fun_tag {}; struct FunTag : proto::terminal< fun_tag > {}; FunTag::type const fun = {{}}; // example usage: fun(); fun('a'); fun('a', 'b'); ...Below is the grammar that matches all the allowable invocations of `fun()`: struct FunCall : proto::function< FunTag, proto::vararg< proto::terminal< char > > > {};The `FunCall` grammar uses _vararg_ to match zero or more character literalsas arguments of the `fun()` function.As another example, can you guess what the following grammar matches? struct Foo : proto::or_< proto::terminal< proto::_ > , proto::nary_expr< proto::_, proto::vararg< Foo > > > {};Here's a hint: the first template parameter to `proto::nary_expr<>` represents thenode type, and any additional template parameters represent child nodes. The answer is that this is a degenerate grammar that matches every possible expression tree, from root to leaves.[endsect][/=============================][section Defining DSEL Grammars][/=============================]In this section we'll see how to use Proto to define a grammar for your DSEL and use it to validate expression templates, giving short, readable compile-time errors for invalid expressions.[tip You might think that this is a backwards way of doing things. ["If Proto let me select which operators to overload, my users wouldn't be able to create invalid expressions in the first place, and I wouldn't need a grammar at all!] That may be true, but there are reasons for preferring to do things this way.First, it lets you develop your DSEL rapidly -- all the operators are there for you already! -- and worry about invalid syntax later.Second, it might be the case that some operators are only allowed in certain contexts within your DSEL. This is easy to express with a grammar, and hard to do with straight operator overloading.Third, using a DSEL grammar to flag invalid expressions can often yield better errors than manually selecting the overloaded operators.Fourth, the grammar can be used for more than just validation. You can use your grammar to define ['tree transformations] that convert expression templates into other more useful objects.If none of the above convinces you, you actually /can/ use Proto to control which operators are overloaded within your domain. And to do it, you need to define a grammar!]In a previous section, we used Proto to define a DSEL for a lazily evaluatedcalculator that allowed any combination of placeholders, floating-pointliterals, addition, subtraction, multiplication, division and grouping. Ifwe were to write the grammar for this DSEL in[@http://en.wikipedia.org/wiki/Extended_Backus_Naur_Form EBNF], it might looklike this:[pregroup ::= '(' expression ')'factor ::= double | '_1' | '_2' | groupterm ::= factor (('\*' factor) | ('/' factor))\*expression ::= term (('+' term) | ('-' term))*]This captures the syntax, associativity and precedence rules of a calculator.Writing the grammar for our calculator DSEL using Proto is /even simpler/.Since we are using C++ as the host language, we are bound to the associativityand precedence rules for the C++ operators. Our grammar can assume them. Also,in C++ grouping is already handled for us with the use of parenthesis, so wedon't have to code that into our grammar.Let's begin our grammar for forward-declaring it: struct CalculatorGrammar;It's an incomplete type at this point, but we'll still be able to use it todefine the rules of our grammar. Let's define grammar rules for the terminals: 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 > {};Now let's define the rules for addition, subtraction, multiplication and division. Here, we can ignore issues of associativity and precedence -- the C++ compiler will enforce that for us. We only must enforce that the arguments to the operators must themselves conform to the `CalculatorGrammar` that we forward-declared above. struct Plus : proto::plus< CalculatorGrammar, CalculatorGrammar > {}; struct Minus : proto::minus< CalculatorGrammar, CalculatorGrammar > {}; struct Multiplies : proto::multiplies< CalculatorGrammar, CalculatorGrammar > {}; struct Divides : proto::divides< CalculatorGrammar, CalculatorGrammar > {};Now that we've defined all the parts of the grammar, we can define`CalculatorGrammar`: struct CalculatorGrammar : proto::or_< Terminal , Plus , Minus , Multiplies , Divides > {};That's it! Now we can use `CalculatorGrammar` to enforce that an expressiontemplate conforms to our grammar. We can use _matches_ and `BOOST_MPL_ASSERT()`to issue readable compile-time errors for invalid expressions, as below: template< typename Expr > void evaluate( Expr const & expr ) { BOOST_MPL_ASSERT(( proto::matches< Expr, CalculatorGrammar > )); // ... }[endsect][endsect][endsect]
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?