expression_tree
3.2
|
An expression tree is a tree that stores data in its leaf nodes and operations in its branch nodes. The tree's value can then be obtained by performing a postorder traversal, applying each branch's operation to its children.
For example, this tree evaluates to (2 * 1 + (2 - x)):
To build an expression tree with expression_tree::tree, you assign function objects to branch nodes and either constant values or pointers to variables to leaf nodes. When your tree is built, call its evaluate member function to get its value.
This implementation:
<future>
header is available.In order to be evaluated, the tree must be correctly formed. That is, all its branch nodes' children nodes must have been given a value.
Two policies are vailable as template parameters to expression_tree::tree. The first enables caching the value of certain branches to avoid unnecessary evaluation. The second enables multi-threaded evaluation.
Caching optimizations rely on the constness of branches. Once a constant branch has been evaluated, it is not required to evaluate it again. A branch is considered constant when all its children are constant, be they branches or leaves. A leaf is considered constant when its value is assigned a constant value rather than a variable value (i.e. a pointer).
The first optimization caches a branch's value when a it is first evaluated. The second, more aggressive optimziation, caches when a branch's children are assigned to. The default policy is no_caching which performs no caching.
By instantiating a tree with its second template parameter set to cache_on_evaluation , a tree's evaluation will be optimzed by caching-on-evaluation. Caching on evaluation simply consists on remembering a branch's value at the time it is evaluated, if that branch is considered constant.
Consider the following tree, where Bn is a branch, Cn is a constant value and xn is a variable value:
When that tree is evaluated, B2's value will be cached because its children are constant. If C2 and C3 don't change (i.e. if they are not assigned a different constant value), the second time the tree is evaluated, the evaluation of B2 will return its cached value. It will not perform its operation on its children again.
Because one of B1 children is not constant, evaluating B1 will always perform its operation on its two children.
By instantiating a tree with its second template parameter set to cache_on_assignment , a tree's evaluation will be optimzed by caching-on-assignment. Caching-on-assignment means that when a branch's children nodes are assigned to, and if those children are constant, the branch's value is evaluated and cached.
Consider the following tree, where Bn is a branch and Cn is a constant value:
Let's assume that that tree is constructed in the following order: B1, C1, B2, C2, C3.
Upon assignment of C3, B2 will be found to be of constant value and be pre-evaluated. This pre-evaluation will continue recursively up the tree for as long as a branch's both children are constant. In this case, B1 will also be pre-evaluated.
If C1 had instead been a variable (e.g. x), only B2 will have been pre-evaluated. Evaluating B1 would then always perform its operation on its two children.
Given the following tree:
Let's assume that that tree is constructed in the following order: B1, C1, B2, C2, ..., Bn, Cn, Cn+1.
Before Cn+1 is assigned to, none of the tree has been pre-evaluated, given that none of its nodes' constness can be confirmed. When Cn+1 is assigned to, Bn will be found constant and be evaluated. Bn-1 will also be found constant and be evaluated. This will continue until entire tree is evaluated. Thus, a single assignment can trigger the equivalent of evaluate() .
This optimization depends on the availability of C++11's <future>
header. It is enabled automatically if your toolchain supports it. To benefit from parallel evaluation, a tree must be instantiated a tree with the parallel policy class.
When enabled, a branch will evaluate one of its children on the current thread and its other child on a separate, hardware permitting. The decision to actually spawn a seperate thread is left to std::async's
implementation. For large enough tree's the hardware limit will be reached and branches will start evaluating their children sequentially regardless of their threading policy.
The default policy is sequential which evalutes the tree sequentially.
Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.