ivl

ivl step 0 - types, scalars

This mini tutorial provides a systematic introduction to ivl programming through a sequence of tiny examples that cover most basic ivl functionalities. This is a good place to quickly start learning and experimenting with ivl. If you are looking for in-depth discussion or more advanced features, try the ivl manual. We are assuming here some basic knowledge of C++. Here is the list of topics for quick reference:

ivl step 0 - types, scalars [this page]
ivl step 1 - arrays
ivl step 2 - indexing
ivl step 3 - two-dimensional arrays
ivl step 4 - n-dimensional arrays

We begin with a kind of hello world stuff. We then discuss preliminary concepts on scalar quantities, including types, scalars, expressions, and type abstraction. The most common objects in ivl are arrays, discussed in ivl step 1 - arrays.

using ivl

Simple. Include the following at the beginning of every header or source file that is to use ivl,

and, because everything in ivl is defined within namespace ivl, life will be much easier if we also specify we will be using this namespace:

Let us try a complete program, e.g. hello.cpp,

which should print out the value of $2$$\pi$$^2$, where constant pi and function sqr have the apparent meanings and are defined for convenience in ivl. It is good practice to limit the scope of using directives as much as possible.

Compilation depends on platform and ivl installation. Assuming default installation on Linux, the following should be enough to compile and run,

printing out

[More generally, the following should be enough to compile whatever our ivl installation type and target directory:

To create makefiles or workspace/project files for different development environments on different platforms, check ivl installation instructions.]

notation

As you may have noticed, we are using the following style

to represent C++ header or source file input in our text editor or development environment, and

to represent what we see on our terminal (command line), either commands or standard output. On the other hand, hypothetical code and terminal input/output that is only included here for discussion, is represented respectively by

and

[by the way, this is the right place to say we are grateful to SyntaxHighlighter]

starting over

In what follows, we assume a single C++ source file, e.g. scalar.cpp, beginning with

and ending below. In between, we are interleaving C++ code in scalar.cpp, discussion on each code fragment, as well as the standard output it is supposed to produce in the terminal when run.

[The ivl manual follows the same principle. In fact, code fragments are automatically validated, compiled and run to generate the output.]

You are expected to compile and run scalar.cpp exactly as we did for hello.cpp above.

[For convenience, you may type e.g.

Then,

is a shortcut to compiling and running scalar.cpp.]

types

All C/C++ numeric data types are supported in ivl. These include integer numbers [char,int,long,...], possibly unsigned [unsigned char,unsigned int,...], machine dependent [size_t,...], or independent [fixed-width] [int8_t,uint8_t,...]. They also include enumerations [enum], booleans [bool], floating point numbers [float,double], and complex numbers over all previous types [std::complex<>].

Non-numeric types including pointers and user-defined unions, structs and classes are also naturally supported as well as C arrays, although their use is quite limited in the presence of ivl arrays. Almost everything in ivl is based on templates and works equally for any given type through type abstraction.

scalars

Primitive C/C++ types like int or double do not enjoy all privileges that composite types like classes do, for instance defining new operators on them.

ivl defines template class scalar, denoted by syntax _[]: if x is of type T, then _[x] is of type scalar<T>. A scalar behaves exactly as its underlying type, e.g. _[3] + 2 is the same as 3 + 2, but adds more functionality in general. For instance, scalars equip primitive numeric types with the exponentiation operator ->*,

which is a more natural way to express $2\pi^2$:

[recall we are showing one line of scalar.cpp and its corresponding standard output when we run it]

But the true power of scalars is not revealed until we try other underlying types, like ivl arrays or (pointers to) structs, classes, or functions. We shall see such examples later on.

expressions

Expressions of numeric data types are formed by combining numeric functions and operators.

ivl numeric functions are based on the standard C++ <cmath> and the STL library, assuming they are optimal, and work for numeric types, as well as scalars and ivl arrays. Even for primitive types, ivl provides extensions [when functions are invoked though namespace ivl] as well as new constants and functions.

For instance, we have already seen [double] constant pi, standing for $\pi$, and function sqr, where sqr(x) stands for x * x, whatever the type of x. Another constant is [double] infty, standing for $+\infty$,

where nan stands for [double] NaN. Other functions new in ivl are for instance sign(), round(), isinf(), isnan(), conj(), angle() [synonym: arg()]. It is more exciting to see examples when discussing arrays. Additional constants and extended functions are defined on complex numbers.

All arithmetic, comparison, logical, bitwise and compound assignment C++ operators are naturally supported in ivl for all numeric types [except ordering operators for complex numbers] as well as scalars and ivl arrays. The same holds for the ivl exponentiation operator ->*.

Array expressions are much more interesting than their scalar counterparts.

complex numbers

A complex number over numeric type T is represented in ivl by std::complex<T>. Apart from the standard [inconvenient] constructor, e.g. std::complex<double> z(2.0,3.0), ivl supports construction through imaginary unit i or j,

as well as convenient standard output through streaming operator <<

All math functions, e.g. trigonometric functions, are defined for real as well as complex numbers, for instance,

confirming that $\cos$$(i) = \,\,$$\cosh$$(1) = \frac{1}{2} ($$e$$ + e^{-1})$.

[and here is the right place to say we are grateful to MathJax]

The same holds for all operators, including the ivl exponentiation operator

We shall see that everything extends to arrays of complex numbers as well.

that's it!

This completes our examples in source file scalar.cpp. All we need to do is close its main() with

But there is one more topic to discuss.

type abstraction

Most ivl operations are defined in terms of numeric functions and operators as building blocks. Once such building blocks are properly defined, ivl operations can be applied to arbitrary, user-defined types.

As an illustration, let us begin a new header file candidate.hpp,

where we consider candidates for a job position. For each candidate, we are given their name, university grade, and references [evaluated, scored, and represented by an integer on a scale from 0 to 10, like the grade]:

Unfair as it may seem, we give priority to grade over references when comparing candidates:

Let us also agree that in printing out a candidate, we only want to see the name:

Now, let us try this in source file candidate.cpp: even if Alice's references are below average,

she will be preferred over Bob because of her slightly better grade:

So, once operator > is defined, max comes for free! Here we have merely verified that ivl::max behaves as nicely as std::max in terms of type abstraction, but the same principle applies throughout ivl, for instance ivl::max applies to arrays as well.