KeyValue
User's Manual - version: 0.3

12. KeyValue's design: The basics

This section covers some basic aspects of KeyValue's design. The material is kept at the minimum just enough to give the reader all she needs to develop her application using KeyValue.

All KeyValue classes, functions, templates, etc. belong to namespace ::keyvalue.

12.1. Basic types

The five so called basic types are:

  • bool;

  • double;

  • ptime;

  • string; and.

  • unsigned int.

Additionally, KeyValue introduces value::Nothing to represent empty data.

To maximize portability, KeyValue uses ::std::string and ::boost::posix_time for strings and times, resp. These types are exported to namespace ::keyvalue where they are called string and ptime resp.

The single-value and multi-type container for basic types (excluding unsigned int) is value::Variant.

12.2. Values

The value assigned to a key is not necessarily a single value::Variant. It may be a container of value::Variants as well. KeyValue provides three such containers:

Actually, bridge and core library developers do not need to care about these containers. Indeed, they are used exclusively inside KeyValue and, at some point, are converted to more standard types. More precisely, a value::Single is converted into an appropriate basic type T while a value::Vector becomes a ::std::vector<T> and a value::Matrix is transformed into a ::std::vector<::std::vector<T>>.

Class value::Value is a single-value and multi-type container for value::Single, value::Vector or value::Matrix.

12.2.1. Hierarchy of types and multi-level implicit conversions

Only value::Values are returned from KeyValue to front-ends. Hence, a series of conversions must be performed when one wants to return a more basic type. For instance, suppose that a double value x must be returned. In that case the sequence of conversions would be:

return value::Value(value::Single(value::Variant(x)));

Statements like the one above would be needed often and this is very annoying. Fortunately, KeyValue implements a hierarchy tree of types that allow for multi-level implicitly conversions. Therefore, in the example above, the compiler automatically replaces the simpler statement

return x;

the one previously shown.

The hierarchy of types constitutes a tree where each node is defined by a specialization of template struct Parent.

12.3. Keys

Initially a key is just a text labeling a value. However, there is more inside a key that just a string can model. Consider the key Dates in the introductory example again. Its associated value is expected to verify certain conditions:

  • The corresponding value::Value is made of ptimes rather than, say, doubles.

  • Given the plural in Dates, one can expect more than one ptime and then, value::Value's content might be a value::Vector (of ptimes).

  • Since each date corresponds to a stock price, these dates cannot be in the future.

  • Additionally, one can expect the dates to be in increasing order.

This kind of information is encapsulated by a certain class. In KeyValue terminology, these classes are called real keys and belong to namespace ::keyvalue::key.

The class key::Key is the base of all real keys. More precisely, real keys derive from key::Traits which, in turn, derives from key::Key.

Actually, key::Traits is a template class depending on a few parameters:

ElementType

Type parameter which defines the type of elements in the output container. It can be bool, double, ptime, string, classes defined by the core library, etc.

ConverterType

This template template parameter[3] defines the class responsible to convert the input value container into a more appropriate type for core library's use. (See Section 12.3.1.)

MapType

This template template parameter[3] tells how each value::Variant object in the input container must be mapped into an ElementType value. (See Section 12.3.2.)

12.3.1. Converter type

Conversions between KeyValue containers value::Single, value::Vector and value::Matrix to more standard types are responsibility of container converter classes.

KeyValue provides three such templates (described below) depending on a parameter ElementType.

key::StdSingle<ElementType>

Converts from value::Single to ElementType.

key::StdVector<ElementType>

Converts from value::Vector to ::boost::shared_ptr<::std::vector<ElementType>>.

key::StdMatrix<ElementType>

Converts from value::Matrix to ::boost::shared_ptr<::std::vector<::std::vector<ElementType>>>.

If the core library uses non-standard containers, then bridge developers have two choices. They can either use the converters above as a first step and then convert again to desired types; or they can implement new container converters that produce the desired types directly from KeyValue containers. The second option is clearly more efficient.

To learn how to implement new container converters, the reading of the reference documentation of three container converters above it strongly advised. Moreover, their implementations can serve as samples for implementing new ones.

12.3.2. Map type

Similarly to lexical conversions but depending on the key, sometimes, each element of the input container must be mapped to a special value. For instance, for a key Month, it may be convenient to map strings "Jan", "Fev", ..., "Dec" into numbers 1, 2, ..., 12. This is an example of key::FlagMap.

Mappings are performed by classes which implement a method to convert from a value::Variant into other types. Actually, they are template classes depending on a parameter named OutputType which defines (but not necessarily matches) the actual output type. The actual output type might be recovered through the member type OutputType_.

The map template classes are the following:

key::NoMap<OutputType>

Through this map, a value::Variant holding a value x is mapped into an object of type OutputType which has the same lexical value as x. Only front-end enabled lexical conversions are considered. For instance, a value::Variant holding either the double 10.1 or the string "10.1" is mapped into the double (OutputType in this case) 10.1.

key::FlagMap<OutputType>

Some string values are accepted others not. The accepted ones are mapped into particular values of type OutputType. In the example of key Month above, OutputType can be double, unsigned int or an enum type.

key::PartialMap<OutputType>

Half way between key::NoMap and key::FlagMap. First, similarly to key::NoMap and considering front-end enabled lexical conversions, it tries to map a value::Variant value into an object of type OutputType which has the same lexical value as x. If it fails, then, like key::FlagMap, it tries to map a string into a corresponding value of type OutputType. For instance, the value for NumberOfEdges (of a regular polygon) must be an unsigned int greater than 2. For some special values (but not all) there correspond a polygon name (e.g. "Triangle" for 3 or "Square" for 4). There is no special name for a regular polygon with 1111 edges.

key::ObjectMap<OutputType>

This is a map where a string identifier is mapped into a pointer to an object of type OutputType. Notice that this is the only map where OutputType and OutputType_ differ.

12.3.3. Generic keys

Some properties are shared by several types of keys. For instance, Price, Weight, Size, etc., accept only positive values. Although one can write one class for each of them, this would imply unecessary code duplication. To avoid this, KeyValue implements a few generic keys and then, only very specific and application-dependent keys need to be written as new real keys.

All generic keys set their label at construction time. They are:

key::Single<OutputType>

Key for a single object of type ElementType. No constraints on the value are set.

Example: A key labeled Number which accepts any double value is defined by

key::Single<double> key("Number");
key::Vector<ElementType>

Key for a vector of objects of type ElementType with no constraints on them. A restriction on the size of the vector might be set on construction.

Example: A key labeled Names which expects a vector of 5 strings is defined by

key::Vector<string> key("Names", 5);
key::Matrix<ElementType>

Key for a matrix of objects of type ElementType with no constraints. A restriction on the matrix dimension can be set at construction.

Example: A key labeled Transformation which accepts a 2x3 matrix is defined by

key::Matrix<double> key("Transformation", 2, 3);
key::Positive

Key for a positive number.

Example: A key labeled Price is defined by

key::Positive key("Price");
key::StrictlyPositive

Key for a strictly positive number.

Example: If the key in the previous example could not accept the value 0, then it would be defined by

key::StrictlyPositive key("Price");
key::Bounded<ElementType, Bound1, Bound2>

Key for a single bounded value of type ElementType. Template template parameters[3] Bound1 and Bound2 define the bound types and can be either key::NoBound, key::Greater, key::Geq (greater than or equal to), key::Less or key::Leq (less than or equal to).

Example: A key labeled Probability accepting any double value from and including 0 up to and including 1 is defined by

key::Bounded<double, key::Geq, key::Leq> key("Probability", 0.0, 1.0);
key::MonotoneBoundedVector<ElementType, Monotone, Bound1, Bound2>

Key for vectors whose elements are monotonic and/or bounded. Template template parameter[3] Monotone defines the type of monotonicity and can be either key::NonMonotone, key::Increasing, key::StrictlyIncreasing, key::Decreasing or key::StrictlyDecreasing. Bound1 and Bound2 are as in key::Bounded. Additionally, a constraint on the vector size can be set at construction.

Example: A key labeled Probabilities accepting 10 strictly increasing numbers from and excluding 0 up to and including 1 is defined by

key::Bounded<double, key::StrictlyIncreasing, key::Greater, key::Leq>
  key("Probabilities", 0.0, 1.0, 10);

12.4. DataSet

Key-value pairs are stored in DataSets. This class implements methods getValue() and find() to retrieve values assigned to keys. Both methods receive a real key and processes all the information about the expected value encapsulated by the key. For instance, suppose the variable today holds the current date and consider a key BirthDates which corresponds to a vector of increasing dates, supposedly, in the past or today.

An appropriate real key is then:

key::MonotoneBoundedVector<ptime, key::Increasing, key::Leq>
  births("BrithDates", today);

Therefore, if key BirthDates belongs to a DataSet data, the result of

data.getValue(births);

is a ::boost::shared_ptr<::std::vector<ptime>> such that the elements of the pointed vector are in increasing order and before (less than or equal to) today. If the input does not verify this constraint, then an exception is thrown to inform the user about the failure. Therefore, the caller does not need to check the constraints.

Since the type returned by getValue() depends on the real key it receives, this method is a template function. The same is true for find().

The difference between getValue() and find() concerns what happens when the key is not resolved. The former method throws an exception to indicate the failure whereas the latter returns a null pointer. In practice, getValue() is used for compulsory keys and find() for optional ones. A typical use of find() follows:

bool foo(false);
if (bool* ptr = data.find(key::Single<bool>("Foo")))
  foo = *ptr;

In the code above foo is false unless key Foo is found in data, in which case, foo gets the given value.

12.5. Processors

All builders and calculators derive from class Processor. This class declares two pure virtual methods: getName() and getResult(). The former method returns the name under which the processor is recognized by key Processor. The second gets the result of processing a DataSet.

Actually, builders and calculators are specializations of template classes Builder and Calculator, resp. They depend on a parameter type named Tag whose primary role is distinguishing different specializations.

Builder and Calculator specializations may implement different features which affects their declarations and implementations. Therefore, different specializations of Builders and Calculators might derive from different base classes and implement different methods. Rather than declaring the specializations from scratch, providing all their base classes and declaring all necessary methods, helper files keyvalue/mngt/DeclareBuilder.h and keyvalue/mngt/DeclareCalculator.h should be used. (See Builder and Calculator for details.)

12.5.1. Commands

A Processor able to process an empty DataSet might be declared a Command. More precisely, if the Processor is Builder<Tag>, then its getObject(const DataSet& data) method might do its job ignoring data (e.g. ListOfDataSets and NumberOfDataSets). Another possibility is when the method does look up values in data but, failing to find any, can still do its job considering default values for the keys, with or without intervention of Default data set.

In the cases above, Builder<Tag> might be declared a Command by deriving from this class. Similar arguments hold for Calculator<Tag>.

When a Processor is a Command, front-ends might take advantage of this fact and provide shortcuts or menu entries to call the Processor without asking for additional input.

12.5.2. Building from a single value

In general, the input data required by Builders are so rich that must be stored in a DataSet. Nevertheless, in some cases, a single value::Variant might be enough. For instance, consider a builder that creates a function given a few points on its graph. Normally, this Builder needs the set of points, an interpolator and an extrapolator. A DataSet is necessary to hold all this information. However, when the function is known to be constant, then a single number ― the constant ― is enough to build the function. Rather than creating a DataSet to store a single double value, it would be more convenient if the Builder could accept just this value (or more generally, a value::Variant). This is, indeed, the case.

Any Builder specialization able to build from a value::Variant must derive from template class BuilderFrom. This template class depends on ObjectType ― the type build by the builder ― and on InputType ― the basic type that the Builder can build from. (In the previous example, InputType would be double.)

12.6. Exceptions and Messages

Message is the abstract class that defines the interface for all types of messages sent to loggers. MessageImpl is a template class which implements Message's pure virtual methods. There are six different specializations of MessageImpl with corresponding typedefs:

They define operator &() to append formated data to themselves. A typical use follows:

Info info(1);  // Create a level-1 Info message.
size_t i;
::std::vector<double> x;
//...
info & "x[" & i & "] = " & x[i] & '\n';

Similarly, exception::Exception is an abstract class whose pure virtual methods are implemented by template class exception::ExceptionImpl. This template class has a member which is an instantiation of MessageImpl. The exact instantiation is provided as a template parameter of exception::ExceptionImpl. There are two specializations of exception::ExceptionImpl with corresponding typedefs:

RuntimeError indicates errors that can be detected only at runtime depending on user-provided data. LogicError indicates errors that should be detected at development time. In other terms, a LogicError means a bug and is thrown when a program invariant fails. It is mainly used indirectly through macro KV_ASSERT as in

KV_ASSERT(i < getSize(), "Out of bound!");

To keep compatibility with exception handlers catching standard exceptions, RuntimeError derives from ::std::runtime_error while LogicError derives from ::std::logic_error.

Method exception::ExceptionImpl::operator &() provides the same functionality of MessageImpl::operator &(). Example:

if (price <= 0.0)
  throw RuntimeError() & "Invalid price. Expecting a positive number. Got " &
    price;

Other more specific exception classes are implemented to indicate errors that need special treatment. They all derive from either RuntimeError or LogicError.



[3] Template template parameters are one of the least known features of C++ and deserve a quick note here. Most template parameters are types. Nevertheless, sometimes a template parameter can be a template, in which case it is referred as a template template parameter. For instance, a template Foo depending on only one template template parameter might be instantiated with Foo<::std::vector> but not with Foo<::std::vector<int>>. Recall that ::std::vector is a template class while ::std::vector<int> is a class.

Valid HTML 4.01 TransitionalValid CSS!