Introduction
------------

The current design  for GSL 1.x containers suffers in
several ways. This topic has been discussed at some length
on the mailing list, so I only summarize here.

  1. The design of vectors and matrices displays little or no commonality
     and makes it difficult to treat them as different types modeling the
     same concept, the "multi-array" concept. Extending the design to
     include multi-arrays and to bring the vectors and matrices into
     the fold requires some deliberate changes.

  2. The design is heap-centric. This has several consequences.

     a. It is difficult to use the components from other languages,
        notably C++, in a normal way. There is no way to factor out
        the heap allocation of GSL containers from their logical
        construction, without bypassing the GSL allocation functions
        entirely, which implies bypassing and replacing the initialization.
        This introduces unacceptable coupling to the details of the GSL
        implementation for any third party library.

     b. Furthermore, the GSL containers appear incongruous in a C++ context,
        because they do not fit the basic C++ standard library container model,
        where the containers are happy to be on the stack. One becomes
        accustomed to
            std::vector<double> v;
        but
            gsl_vector v;       // huh?
            gsl_vector v = ??   // hmmm...

     c. It may not be natural to wrap the container pointers in another
        language context; it is usually more natural to wrap objects
        directly: "has-a" is usually better than "has-a-pointer-to".

  3. The design of the view types is not levelized with the
     non-view types; they do not look/feel the same. Since
     this problem has been exposed directly to clients,
     fixing it requires some incompatible changes effecting
     all users of view types.

  4. The types do not support the most general notion of slicing.
     Operations such as "view the upper sub-diagonal of a matrix"
     are implemented in an ad-hoc manner, if at all.

  5. The matrix type does not support column-major storage/indexing,
     making it difficult to interface with Fortran libraries.


The following describes several possible designs for a container library
addressing these problems. Since there are certainly other problems that
people are having with the GSL 1.x containers, some of these designs may
be better than others for reasons other than those given above. People
should carefully examine these ideas and decide if one or more of them
solve problems which they have experienced, or fail to solve them.


Definitions
-----------

An indexing map is an object used to map a set of indices in an index space
to a scalar offset using a strided access model. The number of indices
(dimension of the index space) is called the rank of the indexing map.
Strided access is defined by the formula

  offset({i[0], ..., i[n]) = sum(r=0,n) stride[r] * i[r]

where the rank of the map is n+1.

A multi-array is an object that manages a reference to a data segment
(the data origin) and an indexing map which is used to address
that data segment by strided access. Multi-array implementations
also share a certain amount of book-keeping data.

A vector is a multi-array of rank one.

A matrix is a multi-array of rank two together with the constraint that
the "fast index" correspond to unit stride. This is necessary for
consistency with the notion of matrix assumed in other
libraries, such as blas and lapack.

An implementation is called "stack-friendly" if the metadata describing
the container requires no heap allocations.



Common Features
---------------

The designs described below share the following common features.

  1. All containers model the multi-array concept.

  2. All containers are physically compatible with the general multi-array
     container, making the design both logically and physically parallel.

  3. This includes the "view" types, so that the designs are properly levelized.

  4. Containers can be initialized and used on the stack or the heap.
     The containers are all "stack-friendly".

  5. All containers support the most general model of slicing consistent
     with strided access.

A common feature _not_ addressed by these designs is the allocation of the
data segments themselves. There should be a general mechanism for replacing
the allocation strategy for containers, on a sufficiently fine-grained
level of detail to be useful.

Conceptually, multi-arrays have statically determined rank. Therefore, it is
natural that each rank corresponds to a separate type. This mirrors tools like
boost::multi_array, where the rank is a template parameter. It is unfortunate
that we do not have a language-defined template mechanism to do the
combinatorial work for us, but we deal with that.

Each design also introduces a general multi-array type, which is logically
and physically used as a "base class" for all containers. Functions written
to operate on general multi-arrays can be applied successfully to any
container object. Dynamic ranks are also supported in a natural way,
although their use implies some well-defined performance penalties
in comparison to the static rank types. Notice also that objects of
a true arbitrary rank type must probably reside on the heap.

The proposed designs provide definitions for static types only to rank = 3,
simply to avoid unenlightening repetition. The designs are easily extended
to an arbitrary (but fixed) maximal static rank. Headers and implementations
could be automatically generated.

Multi-arrays allow for either C or Fortran style storage. In fact, this is
a misnomer; the storage itself is not affected (how could it be). Rather,
the indexing convention is affected. The indexing convention is fixed at
the time the multi-array object is initialized. The convention simply
changes the way that the dimensions/stride array is ordered. It does
not change the way the offsets are computed from the indices and strides.



Common Technical Points
-----------------------

The generic multi-array type is declared with an indexing map of size 1. This is not
intended to indicate use of the struct hack, which would require heap allocation.
Rather, it indicates a general form of layering which is consistent with the
struct hack, but which is actually used to layer over the static rank types.

I have not bothered to declare any functions inline, in order to avoid specifying
implementations. Performance critical functions, such as indexing
calculations, will eventually be inlined.

Integer-valued struct members are generally declared as 'int'. This may not be adequate
for full-on 64 bit use. But I am not concerned about that right now. We can choose an
adequate type later, probably 'long'. Note that we must be careful about signs
(for strides especially), so it is _not_ adequate to drop in size_t everywhere.
Also, I have an aversion to using unsigned types in interfaces, see the discussion
in [Large-Scale C++ Software Design, J. Lakos].

Neither the implementation nor the semantics require indices to be positive. For example,
the rank two index-vector (1, -1) is always valid and represents the last element of
"row" 0. Since this could well be useful, and since the client should not be forced to
forego manipulations of this type, indices are implemented as a signed type. This
slightly restricts the addressable range for a rank one index map, but this is not an
important issue. Again, see Lakos for further motivating discussion as to why interfaces
should not use unsigned types, as a general rule.

I have chosen a return-by-value form for the on-the-stack constructors. It would
be more general to define construct-in-place forms and define return-by-value
and return-off-heap forms in terms of those. This can always be done later. I imagine
that most people will use the return-by-value forms; although they may imply
a small reducible overhead compared to construct-in-place forms, they
give a substantial syntactic benefit. Also, an in-place constructor
must decide what to do about the data field; the pointer may be junk,
or it may be a valid heap location... there is no way to know. In-place
constructors should probably be kept out of the hands of normal users.

Dismantling objects which were built on the stack must be distinguished from
"freeing" objects constructed off the heap. So we have functions to deallocate
the data segment separately.



Design 1
--------

Design 1 introduces two types for each general container concept. These
types are the containers for const and non-const data segments. All types
are logically "views" into their data segments, and there are no explicit
view types. The life-cycle of data segments is controlled by a flag which
indicates whether or not an object is responsible for "free()-ing"
(in general deallocating in some manner) the data segment. This
life-cycle control is analogous to that in the current GSL
containers, where the flag member is typically called 'owner'.

Pros:  1. Behaves like current GSL containers "done right".
       2. Could probably be made (almost) source-compatible with current containers.

Cons:  1. In some ethereal plane of absolute correctness, the physical alignment
          is not guaranteed. However, all known compilers do it right. Furthermore,
          the standards committee believes that a more practical notion of the
          "common initial segment" issue must eventually be addressed so that
          this type of alignment can be guaranteed. So no sane compiler vendor
          will decide tomorrow to do it wrong.
       2. May require casts in a few locations, in order to use methods with
          const-ness guarantees on non-const objects.
       3. The 'owner' flag is basically a type-field, the sort of thing which
          smells bad to anyone with experience in object-oriented design.
          However, it is probably necessary, and it is typical of C OOP.
	  In fact, both the owner and rank act as types fields, in slightly
	  different ways.



Design 1u
---------

This design is similar to Design 1, with two fundamental types for the const
and non-const objects. However, here the fundamental types are declared as unions.
Each union has a tagged member representing the access protocol, such as the required
const-ness. Other tagged members can be used for access to base classes. Any rational
protocol layered over the physical objects can be expressed this way.


Pros: 1. Eliminates the occasional need for casts in client code, compared to Design 1.

Cons: 1. same as Design 1. Cons.1
      2. Requires that access to container members go through the union tags, which
         introduces some syntactic cruft. For example
             gsl_vector v;
             v.impl.data[0] = 0.0;       // as opposed to v.data[0] = 0.0;
             v.const_impl.data[0] = 0.0; // compiler error: attempt to change data through const protocol
             gsl_const_vector cv;
             cv.const_impl.data[0];      // ok
             cv.impl.data[0] = 0.0;      // compiler error, object has no non-const 'impl' protocol
         This cruft is annoying in the case where a cast is not needed in
         Design 1. However, this cruft is also less annoying in the case
         where the cast would be needed. So there is some trade-off.
      3. same as Design 1 Cons. 3

Technical Point:
  Macros could be used to eliminate many of the union tag qualifications. The tags
  are parallel in name, although the enclosing types are different. This means that
  macros could be ideal for simply appending the correct tag name in most places,
  where the tag is easily determined by the method context. A macro would simply
  forward to the appropriately typed method, allowing the client to remain oblivious
  of which protocol (union tag) was required. However, this would require some further
  thought; we can discuss the issues later. This idea is not integral to the design.



Design 2
--------

Design 2 is an older version which removes the 'owner' flag life-cycle control and
replaces it by separate types. This means that three types are required for each
container concept. These are as follows.

  "const_ref": strided access to const data, no ownership
  "ref":       strided access to non-const data, no ownership
  "":               strided access to non-const data, ownership

There is no fourth type because ownership of const-data
is semantically useless. "Arrays are born non-const."

Pros: 1. Removes the 'owner' type-field for life-cycle control.
      2. Parallels the boost::multi_array types, where the three types are needed
         in order to control the behaviour of methods such as assigment and copy,
         in regards to deep-copy vs. shallow-copy semantics.

Cons: 1. same as Design 1 Cons.1.
      2. same as Design 1 Cons.2.
      3. Introduces a third type, requiring a little more intellectual buy-in
         from clients. However, this has the potential to simplify the landscape
         in the end, because of the clear semantic distinctions introduced.


Technical Problems:
      1. Since the types are somewhat freely interchangeable, it actually
         becomes hard to tell the difference between the reference types and
         the true ownership type. How can we have one function to free them
         all, if there is no dynamic way to tell which is which?
         This seems like a show-stopper to me. Therefore this design
         is tentatively REJECTED.
