Previous Up Next

Chapter 3  Intègre: basic data types

3.1  Global overview

3.1.1  What is Intègre ?

Intègre is a safe and efficient data types library, designed for generic algorithm writing. Even if it is now independent from Olena, it was initially developed to provide value types for images' pixels. Intègre implements basics types such as integers, floats, complexes, vectors but also more evolved types such as range (in the same spirit than Ada) or cycle.

By safe we wean that all operations (arithmetic, assignments, etc.) are checked. If there is an overflow, the user is noticed, at compile time if possible, at runtime otherwise.

By efficient we mean that using Intègre additional checks and features should not decrease too much the overall performances. This is why Intègre relies intensively on template code and meta programming.

3.1.2  Interactions with builtin types

Intègre is designed to simplify generic algorithms writing (generic in the sense of data type). But sometimes, one may want to use builtin type too in its algorithms, such as int for example. Intègre provides commodities to accept both Intègre's types and builtin or external types.

3.1.3  Generic algorithms support

Suppose you want to implement a sum which works on several data types:

template <class DataType>
ResultType Sum(DataType vals[10])
{
  ResultType s = zero_for_ResultType();
  for (unsigned i = 0; i < 10; ++i)
    s += vals[i];
  return s;
}

You know that the sum of ten values will certainly not fit into the same data type, so you would like to store the result into a larger data type. The same problem stands for the initial value of the result, 0 is not expressed the same way if we are dealing with integers, complexes or vectors.

Intègre provides an handy way to solve these problems:

template <class DataType>
ntg_cumul_type(DataType) Sum(DataType vals[10])
{
  ntg_cumul_type(DataType) s = ntg_zero_val(DataType);
  for (unsigned i = 0; i < 10; ++i)
    s += vals[i];
  return s;
}

This algorithm will work with almost all Intègre's data types supporting arithmetic addition, but also with c++ builtin types.

You can refer to the following chapters for more informations.

3.1.4  Available types

Here is a list of available types, you can refer to the type reference section for more details about each type.

3.2  Using Intègre

3.2.1  Compilation

Intègre is an active library, and does not provide any object file. It only provides headers containing generic types and functions. So one just have to add a compilation flag to make the compiler find Intègre headers.

3.2.2  Namespace

All Intègre public services are in the ntg namespace.

3.2.3  Includes

Special include files: Other useful include files are mentioned in the documentation of the concerned features.

3.3  Types properties

To simplify generic algorithms implementation, we need a way to get types properties by a generic way. Traits are defined to do this. As they are implemented using traits, it is possible to define properties for builtin or external types. So one can get properties for non Intègre native types.

3.3.1  Naming scheme and conventions

Properties associated to types can take two forms: types or values.

3.3.2  Properties which are types

They are suffixed by _type, for example: larger_type, cumul_type, abstract_type, etc.

Here is the list of common properties:

abstract_type

Empty classes representing the kind of the type. This is useful since builtin can have associated abstract type. As these classes are organized by a hierarchical way, their main interest is static concept checking.

Here are the available abstract types:

ntg_type

Intègre provides a good interaction with builtin types, but sometimes it is useful to get the Intègre's type associated with a builtin one (if it exists). For example, ntg_type for unsigned char is int_u8.

base_type

When using decorators types, such as range or cycle, one may needs to know the original undecorated type. base_type represent this type.

storage_type

Intègre's types generally use builtin types to store their value. For example, int_u8 uses unsigned char to store its actual value. storage_type represent the type used to store values.

3.3.3  Properties which are values

Values properties are implemented by static functions, to ensure a compile time evaluation and to avoid any useless memory usage.

name()

Returns a string with the name of the type. This can be useful for debugging purposes.

3.3.4  Accessing types properties

To access to the properties describe above, there is two ways, using directly type_traits or using macros hiding the access.

type_traits<T>

This is the universal way to access properties. For example, use type_traits<int_u8>::name() or type_traits<unsigned char>::abstract_type to get the name of int_u8 and the abstract_type representing the builtin type unsigned char.

Using type_traits can be rather fastidious (it generally requires a typename keyword), so macros have been defined to simplify accesses. You should use type_traits only when it is not possible to use macros (for example when there is a comma in the name of the type).

Macros

Macros are defined to every possible property. The naming scheme is simple: ntg_property_type(Type) is one wants to access to a type property, ntg_property_val for a value property. Here are a few examples: ntg_max_val(int_u8), ntg_abstract_type(float_s), etc.

3.4  Ensuring programs safety

3.4.1  Concept checking

One may want to write an algorithm working only on integers, whatever its exact type. To accept both builtin types and Intègre types, he has to write something like that:

template <class T>
void algorithm_on_integer(const T& my_int)
{
  // ...
}

There is no way to put a constraints on T since we want to accept builtin types. A good solution to ensure program integrity is to insert a structucal check using the ntg_is_a(T, U) macro. It checks whether the abstract type of T is a subclass of U, and return a metalic boolean (refer to metalic documentation).

So here the user should write:

template <class T>
void algorithm_on_integer(const T& my_int)
{
  ntg_is_a(T, integer)::ensure();
  // ...
}

If ntg_abstract_type(T) is not an integer, compilation will fail with a clear error message.

3.4.2  Overflow checking

Intègre is designed to be a safe data types library. It tries to make programs safer checking and noticing wrong computations. We want to prevent implicit side effects, often really difficult to find out. Here is an example with builtin types:

  int i = 256;
  unsigned char foo = i; // foo == 0

Another one pointing out arithmetic problems:

  unsigned int i = UINT_MAX;
  unsigned int j = 5;
  unsigned long long k = i + j; // k == 4

These behaviors can be really painful for the programmer, and mostly are overflow problems. This is why Intègre introduces various overflow checks for assignments and arithmetic operations.

Sometimes checks have to be dynamic, for example assigning an int_u16 into an int_u8 may be valid, depending on the value of the int_u16. This can only be performed at execution time, when we know the actual value.

Some checks can be avoided however, for example assigning an int_u8 into an int_u16 is always safe. This is the main justification of Intègre strong typed paradigm, we want to avoid a maximal quantity of checks at runtime.

Strong typing and growing types

To keep a maximal amount of static information about variables range of values, arithmetical operations make types growing. For example, adding 2 int_u8 returns an int_u9. Growing rules for each type is detailed in the types reference section.

This results in the avoidance of a lot of dynamic checks.

Disabling dynamic checks

If you know that your programs works, you can disable dynamic checks defining the macro NDEBUG at compile time.

3.4.3  Behaviors

Intègre can detect overflow problems. But what should it do when it detects one ? Here is the reason to live of behaviors.

strict

Stops the program with an error message when a problem is detected.

saturate

Bounds toward the max or the min value of the type, depending on which is the nearest.

Example: int_u<8, saturate> u = 350; // u == 255.

cycle

Assign the value modulo the value range of the type.

Example: int_u<8, saturate> u = 257; // u == 1.

unsafe

Does nothing. Behaves almost the same way than builtin types. You should not use this behavior, but it might be useful in a few special cases.

3.5  Types reference

3.5.1  Hierarchy

Intègre types are organized hierarchically:

3.5.2  bin

3.5.3  cplx

3.5.4  cycle

3.5.5  float_d

3.5.6  float_s

3.5.7  int_s

3.5.8  int_u

3.5.9  range

3.5.10  vec

3.6  FAQ


Previous Up Next