This section covers some basic aspects of KeyValue's design. The material is kept at the minimum just enough to give the reader all he/she needs to use KeyValue.
All KeyValue classes, functions, templates, etc. belong to
namespace ::keyvalue
.
Inspired by spreadsheet applications, KeyValue uses five basic types:
bool;
double;
ptime
; and
string
.
The first two types above are C++ built-in types. The other three are library types.
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 is
value::Variant
.
Values assigned to keys are not always given by a single
value::Variant
. They may be
containers of value::Variant
s. KeyValue provides
three such containers:.
Actually, bridge and core library developers do not need to care
neither about the containers above. 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 a T while a value::Vector
becomes a
::std::vector<T>
and a value::Matrix
is transformed into
::std::vector<std::vector<T> >
.
Class value::Value is a single-value and multi-type
container for value::Single
, value::Vector
or value::Matrix
.
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 variable x
must be returned.
In that case the sequence of conversions would be:
returnvalue::Value
(value::Single
(value::Variant
(x)));
Statements like the one above would be needed very often, which is very annoying. Fortunately, KeyValue implements a hierarchy tree of types that allow for multi-level implicitly conversions. Therefore, in the example above, the simpler statement
return x;
would be implicitly converted by the compiler into the one previously shown.
The hierarchy of types constitutes a tree where each node is
defined by a specialization of template struct Parent
.
Initially a key is just a string
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. The
value associated to it is expected to verify certain
conditions:
The corresponding value::Value
is made of
ptime
s.
One can expect more than one ptime
and, then value::Value
's container might
be a value::Vector
(of
ptime
s).
Since to each date corresponds a stock price, it cannot be in the future.
Additionally, one can expect the dates to be in increasing order.
This kind of information on keys is encapsulated by a certain
class. In KeyValue terminology, those classes are called real key and belong to namespace ::keyvalue::key
.
The class is key::Key
is the base for all real
keys. More precisely, real keys derive from key::Traits
which derives from
key::Key
.
Actually, key::Traits
is a template class
depending on a few template parameters:
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.
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.)
This template template parameter[3] tells how each
value::Variant
in the input
container must be mapped into an ElementType.
(See Section 12.3.2.)
Conversions between KeyValue containers value::Single
, value::Vector
or 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
into
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 implement new container converters, the reading of reference documentation of the three container converters above it strongly advisable. Moreover, their implementations can serve as samples.
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 convenient to map string
s
"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
value into other
type. Actually, they are template classes depending on a parameter
named OutputType which defines but not necessarily
matches the real output type. The real 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
s are accepted. Each
of them is mapped into a particular value of type
OutputType. In the example above
OutputType is double.
key::PartialMap
<OutputType>
A mix between key::NoMap
and
key::FlagMap
. First, like
key::NoMap
, considering
front-end enabled lexical conversions, it tries to map a
value::Variant
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 (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
::boost::shared_ptr<OutputType>
pointing to an object of type OutputType.
Notice that this is the only map where
OutputType and OutputType_ do not
match.
There are some basic properties shared by several types of keys. For instance, Price, Weight, Size, etc., accept only positive number for values. Although one can implement one class for each of them, this would imply in extensive code duplication. Therefore, KeyValue implements a few generic keys which can be used for keys having basic properties. Only very specific and application dependent keys need to be implied as new real keys.
All generic keys set the key label at construction time. They are:
key::Single
<ElementType>
Key for a single value 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 values of type ElementType with no constraints on them. A restriction on the size of the vector might be set at 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 values 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 in 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 values of type
ElementType. Template template
parameters[3]
Bound1 and Bound2 define the bound
types and can be either 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
,
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);
Key-value pairs are stored in DataSet
s. This class implements
methods getValue()
and also
find()
to retrieve values assigned
to keys. Both methods receive a real key and processes all the
information about the expected value encapsulated in it. For instance,
suppose that the variable today
holds the current
date and consider a key BirthDates which
corresponds to a vector of increasing dates, supposedly, in the
past.
An appropriate real key is then:
key::MonotoneBoundedVector
<ptime,key::Increasing
,key::Leq
> births("BrithDates", today);
Therefore, if key BirthDates is in a
DataSet
named
data
, the result of
data.getValue
(births);
is
a ::boost::shared_ptr
< const
::std::vector<ptime> >
such that the elements of
the vector pointed by this pointer are in increasing order and before
(less than or equal to) today
. The caller does not
need to check that.
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 while the latter returns a null pointer. In
practice, getValue()
is used for compulsory
keys and find()
for optional keys. A typical
use of find()
is as
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
which case, foo
gets the given value.
All builders and calculators derive from protocol class
Processor
. This class declares three
pure virtual methods: getName()
and two overloads of
getResult()
. The former method
returns the name under which the processor is recognized by key
Processor. The first overload of
getResult()
takes a DataSet
parameter while the second
one takes a value::Variant
. The difference
between them is explained below.
In general, the information that processors need to perform
their duties is so rich that must be stored in a DataSet
. Nevertheless, in some
particular cases, a single value::Variant
might be enough. For
instance, consider a builder that creates a curve given a few points
on it. Normally, this processor needs the set of points together with
interpolator and extrapolator methods. In this general case, a
DataSet
is necessary to hold all this
information. However, when the curve is known to be constant, then a
single number - the constant - is enough. Rather than creating a
DataSet
to store a single number, it
would be more convenient if the processor could accept just this value
(or more generally, a value::Variant
). This is, indeed, the
case.
Actually, builders and calculators are specializations of
template classes Builder
and Calculator
, resp. They depend on a
parameter type named either OutputType (for a
builder) or Tag (for a calculator). The primary
role of this parameter is to distinguish between different
specializations. Additionally, in the case of a builder, it also
defines the type of object constructed.
The header files of Builder
and Calculator
implement the two
overloads of Processor
::
getResult()
. The first overload
forwards the call to either Builder
<ObjectType>
::
getObject()
or Calculator
<Tag>
::
getValue()
. These methods must be
implemented by bridge developers whenever the template classes are
used.
As explained earlier, processing a value::Variant
does not always make
sense. Therefore, the second overload of Processor
::
getResult()
throws an exception.
When this processing does make sense, then template classes
XBuilder
and XCalculator
which extend their base
classes Builder
and Calculator
, resp., must be used.
Their header files reimplement the second overload of Processor
::
getResult()
forwarding the call to
XBuilder
<ObjectType>
::
getObject()
and XCalculator
<Tag>
::
getValue()
. As before, these two
methods must be implemented by bridge developers whenever the extended
template classes are used.
The abstract class Message
defines the interface for all
types of messages. 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 in template class
exception::ExceptionImpl
. This
template class has a member which is an instance of MessageImpl
. The exact type of this
instance is provided as a template parameter of exception::Exception
. There are two
specializations of exception::Exception
with
corresponding typedefs:
RuntimeError
(having an
Error
member); and
LogicError
(having a
Logic
member).
RuntimeError
indicates errors that
can be detected only at runtime depending on user 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 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 belong to the less
known features of C++ and then 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 while
std::vector<int>
is a
type.