Data Types¶
type_class_t |
data type | type_id_t |
description |
|---|---|---|---|
INTEGER |
uint8 |
UINT8 |
8Bit unsinged integer |
INTEGER |
int8 |
INT8 |
8Bit signed integer |
INTEGER |
uint16 |
UINT16 |
16Bit unsigned integer |
INTEGER |
int16 |
INT16 |
16Bit signed integer |
INTEGER |
uint32 |
UINT32 |
32Bit unsigned integer |
INTEGER |
int32 |
INT32 |
32Bit signed integer |
INTEGER |
uint64 |
UINT64 |
64Bit unsigned integer |
INTEGER |
int64 |
INT64 |
64Bit signed integer |
FLOAT |
float32 |
FLOAT32 |
32Bit IEEE floating point type |
FLOAT |
float64 |
FLOAT64 |
64Bit IEEE floating point type |
FLOAT |
float128 |
FLOAT128 |
128Bit IEEE floating point type |
COMPLEX |
complex32 |
COMPLEX32 |
32Bit IEEE complex float type |
COMPLEX |
complex64 |
COMPLEX64 |
64Bit IEEE complex float type |
COMPLEX |
complex128 |
COMPLEX128 |
128Bit IEEE complex float type |
STRING |
string |
STRING |
string type |
BINARY |
binary |
BINARY |
binary type |
NONE |
none |
NONE |
none type |
An overview of the primitive data types provided by libpnicore.
libpnicore provides a set of data types of well defined size and utility functions related to type management. The basic header file required to use libpnicore s type facilities is
#include <pni/core/types.hpp>
The data types provided by libpnicore include
- numeric types with all their arithmetic operations
- string types (currently only one member)
- and utility types like
binary_t,bool_t, andnone.
All this types together are refered to as primitive types. The numeric types
are ensured to have the same size on each platform and architecture supported
by libpnicore. They are mostly aliases to the types defined by the C standard
library. However, the utility types binary_t, bool_t, and
none are unique to libpnicore and will be explained in more
detail in the last sections of this chapter.
Every type in libpnicore is associated with an ID represented by the
type_id_t enumeration type. Additionally every type belongs to a
particular type class defined by the type_class_t enumeration type.
Table~ref{tab:types:basic_types} gives an overview over the primitive types
provided by libpnicore and their corresponding type_id_t and
type_class_t values.
Compile time type identification¶
To obtain the ID or class of a type at compile time use the
type_id_map or type_class_map type maps.
#include <pni/core/types.hpp>
using namespace pni::core;
//determine the type ID for a given type
type_id_map<float32>::type_id == type_id_t::FLOAT32;
//obtain the class of a particular type
type_class_map<float32>::type_class == type_class_t::FLOAT;
For IDs the other way around is also possible with the id_type_map
#include <pni/core/types.hpp>
using namespace pni::core;
//determine the type for a given ID
id_type_map<type_id_t::FLOAT32>::type data = ...;
For numeric types there are also some other templates for a more detailed type classification
| template | description |
|---|---|
is_integer_type<T>::value |
true if T is an integer type |
is_float_type<T>::value |
true if T is a floating point type |
is_complex_type<T>::value |
true if T is a complex number |
is_numeric_type<T>::value |
true if T is any of the above types |
Identifying types at runtime¶
| data type | string representation |
|---|---|
uint8 |
“uint8”, “ui8” |
int8 |
“int8”, “i8” |
uint16 |
“uint16”, “ui16” |
int16 |
“int16”, “i16” |
uint32 |
“uint32”, “ui32” |
int32 |
“int32”, “i32” |
uint64 |
“uint64”, “ui64” |
int64 |
“int64”, “i64” |
float32 |
“float32”, “f32” |
float64 |
“float64”, “f64” |
float128 |
“float128”, “f128” |
complex32 |
“complex32”, “c32” |
complex64 |
“complex64”, “c64” |
complex128 |
“complex128”, “c128” |
string |
“string”, “str” |
binary |
“binary”, “binary” |
none |
“none” |
Data types and their string representations.
The recommended way to deal with type information at runtime are the
type_id_t enumerations. At some point in time a program might has
to determine the type ID of a variable type or of the element type of a
container. The basic facility to achieve this is the type_id()
function defined in pni/core/type_utils.hpp. The usage of this
function is rather simple as shown here
#incldue<pni/core/types.hpp>
using namespace pni::core;
//one could use this with
auto data = get_data(...);
std::cout<<type_id(data)<<std::endl;
The important thing to notice here is that no matter what type the
get_data() function returns, type_id() will give you the
type ID. In cases where the type ID is given and a classification of the type
has to be made four functions are provided where each takes a type ID as its
single most argument
| function | description |
|---|---|
is_integer(type_id_t)() |
returns true if the type ID refers to an integer type |
is_float(type_id_t)() |
returns true if the type ID refers to a float type |
is_complex(type_id_t)() |
returns true if the type ID refers to a complex type |
is_numeric(type_id_t)() |
returns true if the type ID refers to a numeric type |
Another important scenario is the situation where a user uses the string
representation to tell a program with which type it should work. In such a
situation you either want to convert the string representation of a type into a
value of type_id_t or vica verse. The library provides two
functions for this purpose type_id_from_str() which converts the
string representation of a type to a value of type_id_t and
str_from_type_id() which performs the opposite operation. The usage
of this two guys is again straight forward.
#include <pni/core/types.hpp>
#include <pni/core/type_utils.hpp>
using namespace pni::core;
//get a type id from a string
string rep = "string";
type_id_t id = type_id_from_str("str");
//get a string from a type id
rep = str_from_type_id(type_id_t::FLOAT32);
The binary type¶
In many cases uninterpreted binary data should be transfered from one location
to the other (a typical example would be to copy the content of one file to
another).
Typically one would use a type alias to something like uint8 to
realize such a type. However, this approach has two disadvantages
- as
uint8is a numeric type with all arithmetic operators available which we do not want for uninterpreted binary data - a mere type alias would make
uint8andbinaryindistinguishable and thus we could not specialize template classes for each of them.
Consequently binary was implemented as a thin wrapper around an
appropriately sized integer type with all arithmetic operators stripped away.
A short example of how to use binary is the copy_file.cpp example in
the examples directory of the source distribution of libpnicore.
In lines $8$ and $10$ we include the pni/core/types.hpp header file and
instruct the compiler to use the texttt{pni::core} namespace by default. In
line $12$ a vector type with binary elements is defined and an instance of this
type is allocated in line $24$. In line $27$ data is read from the input file
and stored in the vector. Now, it is clear from here that a vector of type
char would have perfectly served the same purpose. The major
difference is that unlike char binary has absolutely no
semantics. In practice there is nothing much you can do without it rather than
store it back to another stream as it is done in line $33$.
The none type¶
The none type represents the absence of a type. It is a dummy type
of very limited functionality and is mainly used internally by
libpnicore. One major application of the none type is to do default
construction of type erasures (see type_erasures).
For all practical purposes this type can be ignored.
The bool_t type¶
Unlike the C programming language C++ provides a native bool_t type.
Unfortunately the C++ standardization committee made some unfortunate decisions
with bool and STL containers. std::vector for instance
is in most cases specialized for the standard C++ bool type. In
the most common STL implementation std::vector is considered an
array of individual bits. Meaning that every byte in the vector is storing a
total of 8 bool values. Consequently we cannot obtain an address
for a particular bit but only for the byte where it is stored. Hence
std::vector does not provide the data() method
which is required for storage containers used with the mdarray
templates (see arrays).
To overcome this problem a new boolean type was included in libpnicore which
can be converted to bool but uses a single byte for each boolean
value and thus can use the std::vector template. So use the
libpnicore bool_t type whenever working with libpnicore
templates or whenever the address of a container element is required. For all
other purposes the default C++ bool type can be used.
Numeric type conversion¶
libpnicore provides facilities for save numeric type conversion. These functions are not only used internally by the library they are also available to users. The conversion policy enforced by libpnicore is more strict than that of standard C++. For instance you cannot convert a negative integer to an unsigned integer type. The goal of the conversion rules are set up in order to avoid truncation errors as they would typically occur when using the standard C++ rules.
The basic rule for conversion between two integer type A and B is as follows
A value of typeScan only be converted to typeBif the value does not exceed the numeric range of typeB.
A consequence of this rule is that a signed integer can only be converted to an unsigned type if its value is larger than 0. This is different from the standard C++ rule where the unsigned target type will just overflow.
The second basic rule which governs libpnicore s conversion policy is
During a conversion no information must be lost!
Hence, conversion from a floating point type to an integer type is prohibited as it would most likely lead to truncation and thus a loss of information. Conversion from a scalar float value to a complex value is allowed (as long as the first rule applies to the base type of the complex type) but one cannot convert a complex value to a scalar float type.
Several types cannot be converted to anything than themselves
bool_twhich can be only the result of a boolean operation.binaryas this type is considered to be a completely opaque type conversion to any other type is prohibited. Furthermore no type can be converted to binary.stringconversion to string is done exclusively carried out by formatters provided by the IO library.
The library distinguishes between two kinds of type conversion
- unchecked conversion
- the conversion can be done without checking the value
- checked conversion
- the value has to be checked if it fits into the target type.
Table~ref{tab:types:unchecked_conversions} gives an overview between which types conversion is possible and whether unchecked or checked conversion will be used.
| source / target | uint8 | uint16 | uint32 | uint64 | int8 | int16 | int32 | int64 | float32 | float64 | float128 | complex32 | complex64 | complex128 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
uint8 |
unchecked | unchecked | unchecked | unchecked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
uint16 |
checked | unchecked | unchecked | unchecked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
uint32 |
checked | checked | unchecked | unchecked | checked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
uint64 |
checked | checked | checked | unchecked | checked | checked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
int8 |
checked | checked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
int16 |
checked | checked | checked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
int32 |
checked | checked | checked | checked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
int64 |
checked | checked | checked | checked | checked | checked | checked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
float32 |
none | none | none | none | none | none | none | none | unchecked | unchecked | unchecked | unchecked | unchecked | unchecked |
float64 |
none | none | none | none | none | none | none | none | checked | unchecked | unchecked | checked | unchecked | unchecked |
float128 |
none | none | none | none | none | none | none | none | checked | checked | unchecked | checked | checked | unchecked |
complex32 |
none | none | none | none | none | none | none | none | none | none | none | unchecked | unchecked | unchecked |
complex64 |
none | none | none | none | none | none | none | none | none | none | none | checked | unchecked | unchecked |
complex128 |
none | none | none | none | none | none | none | none | none | none | none | checked | checked | unchecked |
Type matrix showing between which types conversion is possible.
The convert() function template¶
At the heart of libpnicore s type conversion system is the cpp{convert} function template. The declaration of the template looks somehow like this
template<typename ST,typename TT> TT convert(const ST &v);
A value of a particular source type (denoted by the template parameter
ST) is passed as an argument to the convert() template. The value
of this argument will then be converted to a value of the target type
TT and returned from the function template.
This function template throws two exceptions
| exception | reason |
|---|---|
type_error |
in situations where the type conversion is not possible |
range_error |
where the source value does not fit into the target type |
The behavior of this function can best be demonstrated examples. .. code-block:: cpp
auto f = convert<float32>(int32(5));
In this example a value of type int32 is successfully converted
to a value of type float32, while
auto f = convert<uint16>(float32(-5)); // throws type_error
leads to type_error. According to the conversion policies mentioned
above a float value cannot be converted to an integer due to truncation issues.
auto f = convert<uint32>(int32(-3)); //throws range_error
range_error will be thrown as a negative value cannot be converted to an
unsigned type. A similar situation would be
auto f = convert<uint8>(int16(10000)); //throws range_error
where range_error would indicate that it is impossible to store a
value of 10000 in an 8-Bit unsigned variable.