The current version was implemented with the GNU C++ compiler, gcc
, up to version 4.3.2. Some older compiler versions, notably those from the 3.3.x series will fail to compile the code.
The implementation of the quantity classes makes heavy use of the `two-level' aspect of C++ [citation Czernecki ...]. As far as possible, static (compile time) information is used about units This information is mainly represented by types manipulated by template metaprogramming techniques. On the other hand, occasionally dynamic access to such information is necessary, for example in routines that perform input from and output to users in a context which is only defined at run time. Such a situation is found e.g. when the user of the final program prescribes a unit for data input, or if a selected unit should be used for output. Then, the required information is only available in the run time situation, and type information can not be used statically.
#include "loki/Typelist.h"
/usr/include
. This would support transparency in the source code, by indicating clearly where the header comes from. In a similar way, the Boost headers should be treated.make doc
target, an existing doxygen tag file BSUtilities.tag
will be deleted upon invication. If the make doc
process end normally, no such tag file will be retained.version.h
. QUANTITIES_VERSION
is a macro set to a string which denotes the version of the current Quantities package. All functions returning version information retrieve this information from version.h
.
The version number of Quantities consists of three numbers ,
, and
, separated by dots:
.
.
, for example 1.2.1. A plus sign
may be appended to this number triple, indicating an interim version, which is being developed out of the version given.
Rational<>
templates (from BSUtilities, TemplateTools.h), parametrized by two long constants, a numerator and a denominator. This provides for exact arithmetics when performing algebraic calculations with the rational numbers, as long as the numerical limits of long objects are not exceeded. All calculations with these Rational<>
numbers are performed at compile time.
The seven Rational<>
objects are appended within a Loki::TypeList
, which is retrieved from a Dimension object as a type DimList
.
Moreover, the individual rational numbers are accessible ...
commensurablility ...
... describe implementation of units ... ... describe implementation of unit low level functions ... ... compile time and run time functionality ...
... alternative units ...
value
), and by std::strings (namestring
, symbolstring
), respectively. In the present implementation, namestring
and symbolstring
are private members of the abstract Quantity
base class, while the value is only defined in the concrete quantity classes being derived from this base class (Variable, Constant, UniqueConstant, see below The Mode). The latter is expected to change in future versions. Access to namestring
and symbolstring
depends on the mode of the quantity (see below).
Quantity<QT, ST>
classes form the abstract base classes for the definition of concrete quantity classes.
.. the DerivedQuantity class ... assembles information (1) as type (like SU) which should be set when using, and (2) as internal static data members (e.g., bool) which does not have to be set, but may rather be defined once and for all.
... the DerivedQuantityTraits traits template ...
... allows an unrestricted number of derived quantities from a single base quantity ...
... UniqueConstant: only one for a particular derived quantity. If several needed: have different DQTs and use same base quantity ...
The value of a quantity is strictly tied to the storage unit. It is not possible to access the value as a number alone without also getting some information about the associated unit, with only three exceptions:
Variable (const ST &value)
Variable (const ST &value, const std::string &namestring, const std::string &symbolstring)
ST value (void) const {return _value;}
value()
is in the storage unit.template<typename U, typename ST = double> struct Standardize; template<typename U, typename ST = double> struct Reverse;
These exceptions should only be used in cases where it is absolutely necessary, for example, when interfacing quantity objects with purely numerical data and calculations. Note, that the third exception above should not be used on a user level at all. Outside of Quantities code conversions should always be done through copy construction or assignment. Copy construction may implicitly be done in calls such as
MinuteTime tmin; Time t (8.5); // assumes s as storage unit: 8.5 s tmin = t.value (Minute()); // converts t into a transient MinuteTime object // for return and transforms the value accordingly
NameString ()
and SymbolString ()
, rather than by static variables to avoid the `static initialization chaos' (or fiasco) (for further information, see http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.13 and the FAQs following that one, or http://www.informit.com/blogs/blog.aspx?uk=30-C-Tips-in-30-Days-Tip-25-overcoming-static-initialization-dependencies .
The name and the symbol are derived at compile time on the basis of the default value given to the base quantity (as defined by the quantity type). The default can be overwritten with the respective value defined by the derived quantity type during a call to the derived quantity's constructor, if OverwriteName or OverwriteSymbol are set to true
, respectively. This allows to give pre-defined values of name and symbol to different quantities derived from a single base quantity. The user may overwrite even these values with a call to a special constructor which expects two std::string arguments for the name and symbol, respectively, for Variable and Constant quantity objects. On the other hand, the name and the symbol of a UniqueConstant can not be changed, they are fixed by the definitions valid for the quantity and the derived quantity type.
All read access functions to name and symbol in any mode of a quantity are public. The respective values can thus be interrogated from everywhere.
In a Variable or a Constant, the write access functions to namestring
and symbolstring
are also public. Thus, the name and the symbol of these quantities can be changed after construction from a user program. In a UniqueConstant, these write access functions are redeclared protected
, which prohibits changes of namestring
and symbolstring
.
... others? ...
Variable (void) // for a Variable Constant (void) // for a Constant
The constructors with a value
Variable (const ST &value) // for a Variable Constant (const ST value) // for a Constant
The constructors with a value, a name and a symbol
Variable (const ST &value, const std::string &namestring, const std::string &symbolstring) // for Variable Constant (const ST &value, const std::string &namestring, const std::string &symbolstring) // for Constant
... constructor with a value and unit object ...
... constructor with a value, unit object, name and symbol ...
... constructor with value and unit symbol ...
... destruction ...
... ok for quantities with same base unit; in general: commensurable quantities (same unit list!) ...
Copy construction from any other quantity object is only possible in specifically defined cases. No general copy constructor is defined for such an operation. If a quantity wishes to perform such a conversion, it has to provide the definition of a templated structure convertValue<>
. The declaration of a general converting copy constructor which was available in earlier versions of Quantities has been removed after version 1.2.1.
On the other hand, such conversions can be defined to also allow different storage units and storage types. For example, when converting temperatures between ThermodynamicTemperature and CelsiusTemperature, the storage units and/or types of the two temperature types may be different. Note, however, that there may be numerical errors when converting between different storage types,
... special case UniqueConstant -> prevent sneaky copy ...
Length l (2.); // construct a length quantity object with value 2.0, the unit Metre // without default values for name and symbol Length w; // define another length quantity object with default values w.name ("width"); // set the name of object w w.symbol ("w"); // set the symbol of object w w = l; // assign l to w
l
. This is most probably not what is expected. A similar situation would arise if assignment is formulated from a GeneratedVariable, which has empty name and symbol strings, as in the following example: Time t (1.); // construct a time variable Length l (2.); // construct a length variable Acceleration a; // default construction of an acceleration variable // has default name and symbol a = l/(t * t); // assignment of temporary GeneratedVariable to a // default name and symbol of object a would be overwritten if // assignment would also consider these variables
The only exception from this rule is the assignment to a target quantity from a C-style string or a std::string. Here, the interpretation of the input string can be modified by stream control objects to also extract a name and a symbol. However, in contrast to the former examples, in these cases the user of the quantity objects can deliberately select the behavior.
The implementation of assignment in Quantities is based on operator=
. It provides several alternatives based on the nature of the source object. Some of these alternatives are not available for all modes of the target quantity. All assignments are available for a Variable quantity object as target. The Constant quantity object declares all assignment operators as private and UniqueConstants do not have assignment operators at all.
The most simple case is assignment between identical types of quantity objects. In this case, the two objects are tested for self assignment by comparing their addresses, and if different, the value of the source object is copied into the target object. No recalculation between units is needed, since the storage units of the two objects are identical. Commensurability does not need to be checked, since this identically typed obkects are always commensurable.
To assign from a quantity object with the same base quantity but possibly different storage type SST, storage unit SSU, and/or derived quantity type SDQT, operator=
is overloaded with a template function
template<template<typename, typename> class SM, typename SST, typename SSU, typename SDQT> Variable & operator= (const SM<Quantity<QT, SST>, DerivedQuantity<QT, SSU, SDQT> > &quantity)
Variable
(target quantity), even if the source quantity was of another mode. The quantity types QT of the source and the target quantity are required to be identical. This ensures that recalculation of the value from the source storage unit SSU into the target storage unit SU is possible, but prohibits assignment from quantity objects having a base quantity which differs by more than the storage type, unit, and/or derived quantity type. Of course, the storage types ST and SST of target and source must be compatible, i.e. assignment of a value in SST to a variable of type ST must be allowed. Commensurability is guaranteed by the identical base quantities. If ST and SST, SU and SSU, and DQT and SDQT, respectively, were identical, the more specialized operator=
discussed above would be used.
If assignment is attempted from a generated quantity object, commensurability must be checked, since this is no longer guaranteed. The overloading operator=
is selected based on the form of the source quantity:
template<typename SDIM, typename SST, typename SSU, typename SDQT> Variable & operator= (const Variable<Quantity<GenericClass<SDIM>, SST>, DerivedQuantity<GenericClass<SDIM>, SSU, SDQT> > &quantity)
GenericClass
template, with the source dimension encoded by SDIM. Except for the case of a generated target quantity, the source storage unit SSU is always diffent from the storage unit of the target. The source storage Type SST may be different from the target storage type, subject to the same restriction as discussed in the previous assignment operator. The source value is recalculated into the target quantity's unit. In this process the commensurability check is performed by comparing the dimensions of the source and the target quantity. If this check fails, a compile time error DimensionError<True>
is produced.
In special cases, assignment from a quantity object which has a different base quantity has to be allowed. An overloaded version of operator=
template<template<typename, typename> class SM, typename SQT, typename SST, typename SSU, typename SDQT> Variable & operator= (const SM<Quantity<SQT, SST>, DerivedQuantity<SQT, SSU, SDQT> > &quantity)
Assignment from a Dynamic quantity object requires special care. In order to allow for a storage types in the source Dynamic object different from that of the target quantity, the overloading member function is a template with the source storage type SST as parameter:
template<typename SST> Variable & operator= (const Dynamic<SST> &dynamic)
isCommensurable()
member function, using *this
as the argument. If the two objects are indeed commensurable, recalculation of the Dynamic object's value into the unit of the target Variable is done. This operation assumes that the Dynamic value is stored with a standardization ratio of unity. If the two objects are incommensurable, a DimensionMismatch
exception is thrown.
Two overload versions of operator=
for assignment from string types to a quantity type of mode Variable are defined. Both use the read_value
member function to convert the string into a value and a unit. The unit is checked for being legally be used together with the target quantity. Assignment from a std::string or a const char *
argument just forwards the string to read_value
. The use of read_value
automatically ensures that stream control objects are honored.
All assignments not listed above are prohibited. This is accomplished by declaring a most general overload of operator=
template <typename T> Variable & operator= (const T &);
T
not discussed before, will result in a compile time error (e.g., `undefined reference' by gcc). In particular, assignment from a numerical value, like double
, int
, etc. is not allowed. This is an intended restriction, since it emphasizes the notion that a quantity always needs a unit attached to be meaningful.
... describe which combinations of types, separated formulations etc. ...
While the addition defined between two quantities is commutative in the sense that the two results of and
always return true when compared by
operator==
, the types of the results can be different if a and b are of different type. The result of an addition is always a Variable
, however, its storage type and storage unit are those of the quantity object on the left hand side of the addition operator.
Most operators return an object different from the objects operated on. The increment/decrement operators are exceptions from this rule.
The (prefix and postfix) increment and decrement operators are implemented according to Dryhurst's suggestions Dewhurst [Dewhurst_2003] (p. 129 and 264ff). In particular, the prefix operations return a reference to its objects, and the postfix operators return a const temporary. The latter prevents that later on the returned object can be assigned to. This would be useless, since the temporary is destroyed anyway. Increment and decrement operators are only implemented for Variable classes. The underlying value in Constant and UniqueConstant classes can not be changed, and, consequently, incrementing and decrementing them is not allowed.
Unary + and - operators are implemented to return an anonymous temporary object of the same type with no change in the case of unary + and only the sign changed in the case of unary -. Similar to the postfix increment and decrement operators, to avoid misuse of the returned temporary this is qualified as const. This mimics the behavior of the predefined unary + and - operators for built-in types (which do return an rvalue).
... exp, log, log10 ... Exponential, logarithmic, and trigonometric functions as defined in C++ are mirrored in the present code. The code needs to check that the argument of all of these functions is dimensionless. Also, the return value of these functions is a GeneratedVariable with all dimension components set to zero. It is thus also dimensionless.
The check for a dimensionless argument quantity is performed at compile time by the helper template CheckDimensionality, which is incorporated into a compile time IF structure. Depending on the outcome of CheckDimensionality, the compiler instantiates a helper function for the respective operation (if the quantity is dimensionless) or CheckDimensionality triggers a compiler error with a corresponding message (DimensionError<true>).
The return variable type is automatically generated using the GenerateVariable facilities by means of a multiplication of the argument quantity's dimension with a Rational<0>. This returns a dimensionless GeneratedVariable type. This construction allows to assign the result of the function to be assigned to another dimensionless variable (e.g., the PhysicalQuantity PureNumber).
Since the functionality described is identical for all the exponential, logarothmic and trigonometric functions, and the necessary code differs only in the name of the respective function in namespace std
to be called (and the name of the helper function), the code generation was wrapped into a macro QUANTITY_VARIABLE_DIMENSIONLESS_MATHFCT(function, helper). The two macro arguments are the names of the namespace std funtion and the helper function, e.g., `exp' and `expHelper'. This allows to avoid repetition of almost identical code. In fact, each function can be programmed as a one-line macro invocation.
The trigonometric functions are specific in the sense that their namespace std operations expect their argument to be in radians. As a result, in general, it is assumed that code as
Time t1 (5.); // t1 = 5 s Time t2 (2.); // t2 = 2 s PureNumber n = sin (t1/t2); // argument of sin is dimensionless: 2.5
An exception is the PhysicalQuantities PlaneAngle class, which defines specializations of all trigonometric helper functions. These prescribe the recalculation of the value in the PlaneAngle object into unit Radian, even if it is stored in a different PlaneAngle unit.
QUANTITY_COMPARISON_OPERATORS
, which takes three arguments: the mode, the operator name and the operator type. Comparison is implemented for Variable
, Constant
, and UniqueConstant
modes separately. The operator name and type cary duplicated information. However, they have to be formulated separately, since the operator name (e.g. operator==
) can not be generated by concatenation from the operator type (e.g., ==
) within the C++ macro preprocessor. The use of macros was deemed necessary in this case, since the code for each operator type and each mode variant of the derived quantities is almost identical (apart from the macro parameters mentioned above. For six comparison operators types and three modes this results in 18-fold replication of code, and 18-fold repetition of the same code changes if some variation in the code is attempted.Comparison involves two objects: a left hand side and a right hand side object. Four overloaded functions are defined for each comparison operator in a particular mode:
Dynamic
quantity object.Any other comparison between objects not listed above triggers a VariableError<true> compiler message.
Each QUANTITY_COMPARISON_OPERATORS
macro invocation expands into (presently) four overloading member functions for different types of right hand side objects.
The following comparison operators are defined:
==
!=
>
<
>=
and<=
UniqueConstant objects can not be serialized, and do not need to be. Their value is stored and fixed upon first construction of the object. Thus, when the object is generated when re-reading the serialized form, all it's contents is already available. Consequently, there is no use to serialize this type of a Quantity.
The current version was implemented with the GNU C++ compiler, gcc
, up to version 4.3.0. Some older compiler versions, notably those from the 3.3.x series will fail to compile the code.
PhysicalQuantitiese provides header files (*
.h) and implementation files (*
.cc) for each physical quantity. Moreover to allow serialization, export code is given in files *_export
.h. The headers and expot files should be included into user code as appropriate. If the conversion between temperature values in different scales is attempted, also header TemperatureConversions.h must be included.
Physical quantities are implemented as template specializations of Quantity<> derived Variable<>, Constant<> or UniqueConstant<> types. In particular, the quantity type is specified, and for each quantity type a QuantityTraits<> traits template is provided. In this template, the Dimension, the UnitType, the UnitList, and the DefaultUnit of the physical quantity are specified.
Fundamental physical constants are implemented as UniqueConstants. Some of them are additionally defined physical quantities with a separate quantity type (e.g., FaradayConstant), others, however, are just UniqueConstants derived from an already existing Quantity<> (e.g., ElementaryCharge is a UniqueConstant using the electricCharge::Quantity base class.
QuantityVector
VariableVector
VariableVectorIterator
There are two possible solutions to implement this:
The first solution leads to slicing problems (see, Dewhurst [Drewhurst_2003] and Sutter/Alexandrescu [Sutter/Alexandrescu_2005]), i.e. assignment of the quantity aggregate to the unerlying std::vector<ST> is possible with concommittant loss of information. The second solution is probably more tedious, because much functionality of the std::vector<> must be reimplemented or made available in passthrough functions. ...
[BoostSerialization] http://www.boost.org/doc/libs/1_35_0/libs/serialization/doc/index.html
[Sutter/Alexandrscu_2005] H. Sutter and A. Alexandrescu, C++ Coding Standards, Addison-Wesley, Boston, 2005.
back to Quantities start page