MakeOperators

Name

MakeOperators - make the operator functions for a given set of classes that return expression trees, so that PETE can be used with those classes. Also can construct global assignment operators and operator tag structs.

Synopsis

MakeOperators [--help] [--pete-help] [--classes classfile]
[--operators opfile [--pete-ops] ] [--guard INCLUDE_GUARD]
[--scalars] [--extra-classes] [--no-expression] [--assign-ops]
[--op-tags] [--no-shift-guard] [--o outputfile]
    

Description

In order to use PETE with a given set of container classes, operators such as +, -, *, / etc. must be defined to return expression template parse trees when given those classes. Operators must be defined for combining the container classes, B + C, for combining the container classes with scalars, 2 * C, and for combining parse trees with other objects, B + (C + D) + 2. To generate the PETE built-in operators requires over 200 different templated operator functions to interface PETE with a single container class such as the STL vector.

Command line options are:

--help
--pete-help

Print a simple summary of the command options.

--classes classfile

Input the class definitions from the file "classfile". Omitting this option causes no operator functions to be produced, which can be useful if you only want to produce operator tags.

To understand the format of the input file, consider the STL vector. MakeOperators will output definitions for operator+() between vectors and vectors and between vectors and scalars:

template<class T1,class Allocator1,class T2,class Allocator2>
(parse tree return type)
operator+(const vector<T1,Allocator1> &v1,const vector<T2,Allocator2> &v2)
{
  (construct parse tree)
}
template<class T1,class T2,class Allocator2>
(parse tree return type)
operator+(const T1 &v1,const vector<T2,Allocator2> &v2)
{
  (construct parse tree)
}
template<class T1,class Allocator1,class T2>
(parse tree return type)
operator+(const vector<T1,Allocator1>& v1,const T2 &v2)
{
  (construct parse tree)
}
      

In order to construct the operator, the tool needs to know the template arguments "class T,class Allocator" and the templated form of the class "vector<T,Allocator>". For the STL vector example the class definitions file would contain the four lines:

classes
-----
  ARG   = "class T[n],class Allocator[n]"
  CLASS = "vector<T[n],Allocator[n]>"
      

The string [n] needs to be attached to each template argument and represents a number that allows MakeOperators to uniquely identify each argument in binary and trinary operators. For classes with no template arguments, use ARG = "". In general, the class definition definition file can look like:

classes
-----
  ARG   = (class 1 args)
  CLASS = (class 1 definition)
-----
  ARG   = (class 2 args)
  CLASS = (class 2 definition)
...

extraClasses
-----
  ARG   = (extra class 1 args)
  CLASS = (extra class 1 definition)
...
scalars
-----
  ARG   = (scalar 1 args)
  CLASS = (scalar 1 definition)
...
      

When multiple classes are listed, operators are produced for all combinations of those classes with each other, with scalars and with expression objects.

The second optional list starting with the word extraClasses is used if you want to extend a previously created file. For example, if you produced a file defining all the operators for vector<T> and wanted to extend your implementation to operations between vectors and list<T>, then you would list vector as a class and list under extraClasses and specify the option --extra-classes. The resulting file would define operations between lists and lists, and between lists and vectors, but omit those between vectors and vectors, so that you could include both the new file and your previously generated file. Typically, it would be better to simply create a new file with all the operators, so extraClasses should rarely be used.

The final part of this list that begins with the word scalars will only rarely need to be used. By the rules of partial specialization, if any class does not appear in the classes list, it will be treated as a scalar. Suppose you were to define a tensor class Tensor<T>, then Tensor<T>() + vector<Tensor<T> >() would invoke the right function: T1 + vector<T2> (which means treat the tensor on the left as a scalar and add it to each of the tensors in the vector of tensors). A problem arises if you also define scalar operations with tensors of the form Tensor<T1> + T2 to represent adding a scalar to each of the tensor components. In this case Tensor<T>() + vector<Tensor<T> >() is ambiguous as it matches the function for adding scalars to vectors and the function for addint tensors to scalars. To resolve this case, we must explicitly define Tensor<T> + vector<Tensor<T> >, which will happen if we add Tensor<T> to the list of scalars. (So the list of scalars only needs to contain classes that act like scalars but that also define operations between themselves and classes of arbitrary type.)

--o outputfile

Send MakeOperators output to outputfile; otherwise write to stdout.

--operators opfile

Include the operator descriptions from the file "opfile". Typically this option should be omitted, in which case the set of 45 PETE built-in operators are used. See the file src/Tools/PeteOps.in in the PETE distribution to see operator descriptors for all the PETE built-in operators. The general format of an operator descriptor file is:

type1
-----
  TAG      = "tag"
  FUNCTION = "function"
  EXPR     = "expression"
-----
  TAG      = "tag"
  FUNCTION = "function"
  EXPR     = "expression"
...

type2
-----
  TAG      = "tag"
  FUNCTION = "function"
  EXPR     = "expression"
...
      

The string "tag" is the name of a tag class that is used in expression template nodes to differentiate between the different operators. For example, "OpAdd" is used for binary operator+(), "OpSubtract" is used for binary operator-(), and so on. The string "function" is the name of the operator funtion, "operator+" for example. The string "expression" contains the body of a function that evaluates the operator on specific elements. The string should use the names a, b, and c to represent the arguments to the function. For example the definition of binary operator+() sets EXPR = "return (a + b);".

The headings type1, type2, etc. are operator types. Currently the following operator types are supported:

--pete-ops

Using the --operators causes the tool to use operator descriptors from a file, but not to use any of the pre-defined PETE operators. If you wish produce operators for BOTH operators read from a file AND the pre-defined PETE operators, the use --pete-ops as well as the --operators option. For example, the first two commands in the POOMA example below could be simplified to produce one file:

MakeOperators --classes PoomaClass.in \
              --operators PoomaOps.in --pete-ops \
              --guard POOMA_ARRAY_ARRAYOPERATORS_H \
              --no-expression --o ArrayOperators.h
      

--guard INCLUDE_GUARD

The code output by MakeOperators includes ifdefs to guard against multiple inclusion of the form:

#ifndef INCLUDE_GUARD
#define INCLUDE_GUARD
 (code goes here)
#endif // INCLUDE_GUARD
      

If this option is omitted then INCLUDE_GUARD will default to either GENERATED_OPERATORS_H if the --classes option is present, or OPERATOR_TAGS_H otherwise. If you wish to omit the include guards from the output file, then use the option --guard "".

--scalars

When this option is present, only operations between classes and scalars are produced. This option is useful in the situation mentioned in the description of --classes, where operators must be defined between containers and user defined scalars in order to resolve ambiguities. In the example of a user defined tensor class, the user would probably only define a small set of operations with general scalars, like +, -, *, and /. To produce smaller operator files, you could produce all the operators without tensors and then produce the operators between containers and tensors just for the smaller set of operators. See the example section for an example of this case.

--extra-classes

When this option is present, only operations involving extraClasses are produced. This option is useful if you want to create an operator file that extends a previously created operator file. See the --classes option for a description of extraClasses.

--no-expression

MakeOperators needs to define operations between parse tree objects and containers and scalars. In the expression A + ( B + C ), the subexpression ( B + C ) returns a parse tree object which must then be combined with the container A. Some users of PETE, like POOMA, wrap the result of operators inside their own container class, so there is no need to define such operators. (The sum of two POOMA arrays is an array containing an expression.) This flag turns off generation of operations with parse tree objects.

--assign-ops

Generate global assignment operators that call the function evaluate().

--op-tags

Produce definitions of the operator tag classes. PETE already contains definitions of all the PETE built-in operators, so this flag only needs to be used for user defined operators.

--no-shift-guard

It is typical to define the operator ostream << container, which can get confused with the operator T << container under some circumstances. To avoid this problem, PETE only defines the shift operators between scalars and containers if the macro PETE_ALLOW_SCALAR_SHIFT is defined. If --no-shift-guard is selected, then the ifdefs that implement this guard are eliminated and shift operators between scalars and containers are always defined.

Examples

Here we build operators to use STL vectors with PETE. The flag --assign-ops is present because we cannot define the assignment member functions for STL vectors.

MakeOperators --classes vectorDefs.in --assign-ops > VectorOperators.h

For POOMA, we create the built-in PETE operators, some special POOMA operators like real(), and finally operators between POOMA arrays and the POOMA Vector and Tensor scalars to disambiguate them. The flag --no-expression is used because POOMA wraps expressions inside POOMA arrays. The flag --assign-ops not used because POOMA arrays define assignment member functions. In the second command, --op-tags is used because the POOMA operator tag classes need to be defined. In the third command, --scalars is used because the first command has already defined operations between POOMA arrays for the operators in PoomaVectorOps.in (which is a subset of the PETE operators). In the fourth command, --o VectorOperators.h sends output to that file rather than stdout.

MakeOperators --classes PoomaClass.in --guard POOMA_ARRAY_ARRAYOPERATORS_H \
              --no-expression > ArrayOperators.h

MakeOperators --classes PoomaClass.in --operators PoomaOps.in \
              --guard POOMA_POOMA_POOMAOPERATORS_H --no-expression \
              --op-tags > PoomaOperators.h

MakeOperators --classes PoomaVectorClass.in --operators PoomaVectorOps.in \
              --guard POOMA_POOMA_VECTOROPERATORS_H --no-expression --scalars \
              > VectorOperators.h

MakeOperators --classes PoomaVectorClass.in --operators PoomaVectorOps.in \
              --guard POOMA_POOMA_VECTOROPERATORS_H --no-expression --scalars \
              --o VectorOperators.h
    


[Home]
Copyright © Los Alamos National Laboratory 1999